From 389a141b901e52cd482ee004ecc610ad06e58b8e Mon Sep 17 00:00:00 2001 From: Maksim Alzhanov Date: Mon, 18 Nov 2024 20:14:52 +0300 Subject: [PATCH] Sync 2024-11-18 --- .dockerignore | 2 + .editorconfig | 2 +- .eslintignore | 1 - .eslintrc | 9 +- .gitignore | 1 + Dockerfile | 7 +- WebDriverAgent/.azure-pipelines.yml | 28 +- WebDriverAgent/CHANGELOG.md | 36 + .../Configurations/IOSSettings.xcconfig | 2 +- .../Configurations/TVOSSettings.xcconfig | 2 +- .../WebDriverAgent.xcodeproj/project.pbxproj | 24 +- .../Categories/XCUIApplication+FBHelpers.h | 6 + .../Categories/XCUIApplication+FBHelpers.m | 53 +- .../Categories/XCUIElement+FBTyping.m | 2 +- .../Commands/FBDebugCommands.m | 7 +- .../Commands/FBSessionCommands.m | 4 + WebDriverAgent/WebDriverAgentLib/Info.plist | 46 +- .../WebDriverAgentLib/Routing/FBSession.m | 7 +- .../Utilities/FBBaseActionsParser.m | 32 - .../Utilities/FBConfiguration.h | 13 +- .../Utilities/FBConfiguration.m | 16 + .../WebDriverAgentLib/Utilities/FBSettings.h | 1 + .../WebDriverAgentLib/Utilities/FBSettings.m | 1 + .../IntegrationTests/FBVideoRecordingTests.m | 2 + .../XCUIApplicationHelperTests.m | 27 +- WebDriverAgent/lib/types.ts | 1 + WebDriverAgent/package.json | 4 +- bin/package.json | 3 + bin/stf | 2 - bin/stf.mjs | 3 + build.sh | 4 +- doc/units.md | 65 + docker-compose-dev.yaml | 12 +- docker-compose-prod.yaml | 49 +- docker-compose-test.yaml | 16 +- gulpfile.js | 54 +- lib/cli/api/index.js | 80 +- lib/cli/app/index.js | 58 +- lib/cli/auth-ldap/index.js | 150 +- lib/cli/auth-mock/index.js | 84 +- lib/cli/auth-oauth2/index.js | 150 +- lib/cli/auth-openid/index.js | 88 +- lib/cli/auth-saml2/index.js | 98 +- lib/cli/device/index.js | 284 +- lib/cli/doctor/index.js | 16 +- lib/cli/generate-fake-device/index.js | 20 +- lib/cli/generate-fake-group/index.js | 20 +- lib/cli/generate-fake-user/index.js | 20 +- lib/cli/generate-service-user/index.js | 46 +- lib/cli/groups-engine/index.js | 40 +- lib/cli/index.js | 74 +- lib/cli/ios-device/example | 4 +- lib/cli/ios-device/index.js | 220 +- lib/cli/ios-device/install-wda.sh | 33 + lib/cli/ios-device/run-wda.sh | 61 +- lib/cli/ios-device/start-ios.sh | 6 +- lib/cli/local/index.js | 476 +- lib/cli/log-mongodb/index.js | 20 +- lib/cli/migrate-to-mongo/index.js | 18 +- lib/cli/migrate-to-mongo/rdb.js | 68 +- lib/cli/migrate/index.js | 80 +- lib/cli/poorxy/index.js | 76 +- lib/cli/processor/index.js | 38 +- lib/cli/provider/index.js | 298 +- lib/cli/reaper/index.js | 38 +- lib/cli/storage-plugin-apk/index.js | 50 +- lib/cli/storage-plugin-image/index.js | 58 +- lib/cli/storage-s3/index.js | 42 +- lib/cli/storage-temp/index.js | 136 +- lib/cli/triproxy/index.js | 38 +- lib/cli/vnc-device/example | 4 +- lib/cli/vnc-device/index.js | 314 +- lib/cli/websocket/index.js | 62 +- lib/db/api.js | 3371 ++++ lib/db/api.mjs | 2872 --- lib/db/index.js | 82 + lib/db/index.mjs | 77 - lib/db/setup.js | 45 + lib/db/setup.mjs | 45 - lib/db/tables.js | 86 + lib/db/tables.mjs | 86 - lib/units/api/controllers/autotests.js | 254 +- lib/units/api/controllers/devices.js | 484 +- lib/units/api/controllers/groups.js | 505 +- lib/units/api/controllers/stats.js | 12 +- lib/units/api/controllers/user.js | 677 +- lib/units/api/controllers/users.js | 194 +- lib/units/api/helpers/ip-range-check/index.js | 3 +- lib/units/api/helpers/securityHandlers.js | 103 +- lib/units/api/index.js | 109 +- lib/units/api/paths/autotests.js | 4 +- .../api/paths/autotests/install/{serial}.js | 2 +- lib/units/api/paths/autotests/useDevice.js | 2 +- lib/units/api/paths/devices.js | 4 +- lib/units/api/paths/devices/adbRange.js | 2 +- lib/units/api/paths/devices/groups/{id}.js | 4 +- lib/units/api/paths/devices/{serial}.js | 6 +- .../api/paths/devices/{serial}/adbPort.js | 2 +- .../api/paths/devices/{serial}/bookings.js | 2 +- .../api/paths/devices/{serial}/groups.js | 2 +- .../api/paths/devices/{serial}/groups/{id}.js | 4 +- lib/units/api/paths/devices/{serial}/owner.js | 2 +- lib/units/api/paths/devices/{serial}/size.js | 2 +- lib/units/api/paths/devices/{serial}/type.js | 2 +- .../devices/{serial}/updateStorageInfo.js | 2 +- lib/units/api/paths/groups.js | 6 +- lib/units/api/paths/groups/{id}.js | 6 +- lib/units/api/paths/groups/{id}/devices.js | 6 +- .../api/paths/groups/{id}/devices/{serial}.js | 6 +- lib/units/api/paths/groups/{id}/users.js | 6 +- .../api/paths/groups/{id}/users/{email}.js | 6 +- lib/units/api/paths/stats.js | 2 +- lib/units/api/paths/user.js | 2 +- lib/units/api/paths/user/accessTokens.js | 6 +- lib/units/api/paths/user/accessTokens/{id}.js | 4 +- lib/units/api/paths/user/adbPublicKeys.js | 4 +- lib/units/api/paths/user/devices.js | 4 +- lib/units/api/paths/user/devices/{serial}.js | 6 +- .../user/devices/{serial}/remoteConnect.js | 4 +- lib/units/api/paths/user/fullAccessTokens.js | 2 +- lib/units/api/paths/users.js | 4 +- lib/units/api/paths/users/alertMessage.js | 4 +- .../api/paths/users/grantAdmin/{email}.js | 2 +- lib/units/api/paths/users/groupsQuotas.js | 2 +- .../api/paths/users/revokeAdmin/{email}.js | 2 +- lib/units/api/paths/users/service/{email}.js | 2 +- lib/units/api/paths/users/{email}.js | 6 +- .../api/paths/users/{email}/accessTokens.js | 6 +- .../paths/users/{email}/accessTokens/{id}.js | 4 +- lib/units/api/paths/users/{email}/devices.js | 2 +- .../paths/users/{email}/devices/{serial}.js | 6 +- .../{email}/devices/{serial}/remoteConnect.js | 4 +- .../api/paths/users/{email}/groupsQuotas.js | 2 +- lib/units/api/swagger/api_v1.yaml | 590 +- lib/units/app/index.js | 55 +- lib/units/app/middleware/auth.js | 55 +- lib/units/app/middleware/webpack.js | 20 +- lib/units/auth/ldap.js | 76 +- lib/units/auth/mock.js | 66 +- lib/units/auth/oauth2/index.js | 26 +- lib/units/auth/openid.js | 20 +- lib/units/auth/saml2.js | 32 +- lib/units/base-device/support/channels.js | 12 +- lib/units/base-device/support/push.js | 30 +- lib/units/base-device/support/router.js | 14 +- lib/units/base-device/support/sub.js | 44 +- lib/units/device/index.js | 88 +- lib/units/device/plugins/account.js | 456 +- lib/units/device/plugins/bluetooth.js | 110 +- lib/units/device/plugins/browser.js | 182 +- lib/units/device/plugins/cleanup.js | 102 +- lib/units/device/plugins/clipboard.js | 70 +- lib/units/device/plugins/connect.js | 338 +- lib/units/device/plugins/filesystem.js | 104 +- lib/units/device/plugins/forward/index.js | 268 +- lib/units/device/plugins/group.js | 345 +- lib/units/device/plugins/heartbeat.js | 18 +- lib/units/device/plugins/install.js | 359 +- lib/units/device/plugins/logcat.js | 204 +- lib/units/device/plugins/logger.js | 14 +- lib/units/device/plugins/mobile-service.js | 30 +- lib/units/device/plugins/mute.js | 10 +- lib/units/device/plugins/reboot.js | 36 +- lib/units/device/plugins/remotedebug.js | 24 +- lib/units/device/plugins/ringer.js | 74 +- lib/units/device/plugins/screen/capture.js | 92 +- lib/units/device/plugins/screen/options.js | 18 +- lib/units/device/plugins/screen/stream.js | 870 +- .../device/plugins/screen/util/banner.js | 110 +- lib/units/device/plugins/sd.js | 38 +- lib/units/device/plugins/service.js | 1133 +- lib/units/device/plugins/shell.js | 108 +- lib/units/device/plugins/solo.js | 46 +- lib/units/device/plugins/store.js | 48 +- lib/units/device/plugins/touch/index.js | 752 +- lib/units/device/plugins/util/data.js | 18 +- lib/units/device/plugins/util/display.js | 92 +- lib/units/device/plugins/util/flags.js | 20 +- lib/units/device/plugins/util/identity.js | 24 +- lib/units/device/plugins/util/phone.js | 26 +- lib/units/device/plugins/util/urlformat.js | 44 +- lib/units/device/plugins/vnc/index.js | 384 +- .../device/plugins/vnc/util/connection.js | 414 +- lib/units/device/plugins/wifi.js | 74 +- lib/units/device/resources/minicap.js | 218 +- lib/units/device/resources/minirev.js | 130 +- lib/units/device/resources/minitouch.js | 136 +- lib/units/device/resources/scrcpy.js | 172 +- lib/units/device/resources/service.js | 146 +- lib/units/device/support/abi.js | 60 +- lib/units/device/support/adb.js | 30 +- lib/units/device/support/properties.js | 110 +- lib/units/device/support/sdk.js | 40 +- lib/units/device/support/storage.js | 66 +- lib/units/groups-engine/index.js | 26 +- lib/units/groups-engine/scheduler/index.js | 106 +- lib/units/groups-engine/watchers/devices.js | 16 +- lib/units/groups-engine/watchers/groups.js | 34 +- lib/units/groups-engine/watchers/users.js | 2 +- lib/units/ios-device/index.js | 64 +- lib/units/ios-device/plugins/clipboard.js | 30 +- lib/units/ios-device/plugins/devicelog.js | 142 +- .../ios-device/plugins/devicenotifier.js | 58 +- lib/units/ios-device/plugins/group.js | 426 +- lib/units/ios-device/plugins/heartbeat.js | 18 +- lib/units/ios-device/plugins/info/index.js | 50 +- lib/units/ios-device/plugins/install.js | 192 +- lib/units/ios-device/plugins/logger.js | 14 +- lib/units/ios-device/plugins/reboot.js | 38 +- lib/units/ios-device/plugins/remotedebug.js | 24 +- lib/units/ios-device/plugins/screen/stream.js | 167 +- lib/units/ios-device/plugins/solo.js | 60 +- lib/units/ios-device/plugins/util/iosutil.js | 252 +- lib/units/ios-device/plugins/wda.js | 196 +- lib/units/ios-device/plugins/wda/WdaClient.js | 982 +- lib/units/ios-device/support/storage.js | 62 +- lib/units/log/mongodb.js | 10 +- lib/units/poorxy/index.js | 6 + lib/units/processor/index.js | 463 +- lib/units/provider/index.js | 120 +- lib/units/ratelimit/index.js | 14 +- lib/units/reaper/index.js | 38 +- lib/units/storage/plugins/apk/index.js | 32 +- lib/units/storage/plugins/image/index.js | 36 +- lib/units/storage/s3.js | 95 +- lib/units/storage/temp.js | 156 +- lib/units/vnc-device/index.js | 58 +- .../vnc-device/plugins/devicenotifier.js | 82 +- lib/units/vnc-device/plugins/group.js | 314 +- lib/units/vnc-device/plugins/heartbeat.js | 26 +- lib/units/vnc-device/plugins/info/index.js | 96 +- lib/units/vnc-device/plugins/logger.js | 36 +- lib/units/vnc-device/plugins/screen/stream.js | 146 +- lib/units/vnc-device/plugins/solo.js | 70 +- lib/units/vnc-device/plugins/util.js | 76 +- lib/units/websocket/index.js | 1574 +- lib/units/websocket/middleware/auth.js | 50 +- lib/util/apiutil.js | 263 +- lib/util/asciiparser.js | 12 +- lib/util/bundletool.js | 54 +- lib/util/devutil.js | 124 +- lib/util/fakedevice.js | 79 +- lib/util/fakegroup.js | 14 +- lib/util/fakeuser.js | 2 +- lib/util/grouputil.js | 32 +- lib/util/instrument.mjs | 23 + lib/util/jwtutil.js | 6 +- lib/util/keyutil.js | 910 +- lib/util/ldaputil.js | 8 +- lib/util/lockutil.js | 58 +- lib/util/logger.js | 139 +- lib/util/pathutil.cjs | 5 + lib/util/procutil.js | 22 +- lib/util/riskystream.js | 4 +- lib/util/serviceuser.js | 2 +- lib/util/srv.js | 22 +- lib/util/streamutil.js | 10 +- lib/util/timeutil.js | 16 +- lib/util/urlutil.js | 2 +- lib/util/zmqutil.js | 22 +- lib/wire/index.js | 16 +- lib/wire/router.js | 2 +- lib/wire/util.js | 2 - lib/wire/wire.proto | 53 +- package-lock.json | 5053 ++++-- package.json | 21 +- res/app/app.js | 58 +- .../stf/admin-mode/admin-mode-directive.js | 16 +- .../stf/admin-mode/admin-mode-spec.js | 2 +- res/app/components/stf/admin-mode/index.js | 2 +- .../angular-draggabilly-directive.js | 24 +- .../angular-draggabilly-spec.js | 16 +- .../stf/angular-draggabilly/index.js | 8 +- .../angular-packery-directive.js | 105 +- .../angular-packery/angular-packery-spec.js | 19 +- .../components/stf/angular-packery/index.js | 10 +- .../stf/app-state/app-state-provider.js | 42 +- res/app/components/stf/app-state/index.js | 2 +- .../stf/basic-mode/basic-mode-directive.js | 12 +- .../stf/basic-mode/basic-mode-spec.js | 19 +- res/app/components/stf/basic-mode/index.js | 2 +- .../stf/browser-info/browser-info-service.js | 96 +- .../stf/browser-info/browser-info-spec.js | 10 +- res/app/components/stf/browser-info/index.js | 2 +- .../column-choice/column-choice-directive.js | 26 +- res/app/components/stf/column-choice/index.js | 4 +- .../badge-icon/badge-icon-directive.js | 16 +- .../common-ui/badge-icon/badge-icon-spec.js | 19 +- .../stf/common-ui/badge-icon/index.js | 2 +- .../blur-element/blur-element-directive.js | 51 +- .../blur-element/blur-element-spec.js | 19 +- .../stf/common-ui/blur-element/index.js | 2 +- .../clear-button/clear-button-directive.js | 12 +- .../clear-button/clear-button-spec.js | 30 +- .../stf/common-ui/clear-button/index.js | 2 +- .../common-ui/counter/counter-directive.js | 114 +- .../stf/common-ui/counter/counter-spec.js | 19 +- .../components/stf/common-ui/counter/index.js | 2 +- .../enable-autofill-directive.js | 99 +- .../enable-autofill/enable-autofill-spec.js | 19 +- .../stf/common-ui/enable-autofill/index.js | 4 +- .../error-message/error-message-directive.js | 24 +- .../error-message/error-message-spec.js | 19 +- .../stf/common-ui/error-message/index.js | 2 +- .../fallback-image-directive.js | 14 +- .../fallback-image/fallback-image-spec.js | 19 +- .../stf/common-ui/fallback-image/index.js | 2 +- .../filter-button/filter-button-directive.js | 14 +- .../filter-button/filter-button-spec.js | 19 +- .../stf/common-ui/filter-button/index.js | 2 +- .../focus-element/focus-element-directive.js | 53 +- .../focus-element/focus-element-spec.js | 19 +- .../stf/common-ui/focus-element/index.js | 2 +- .../help-icon/help-icon-directive.js | 18 +- .../stf/common-ui/help-icon/index.js | 2 +- .../icon-inside-input-directive.js | 30 +- .../icon-inside-input-spec.js | 19 +- .../stf/common-ui/icon-inside-input/index.js | 2 +- .../include-cached/compile-cache-service.js | 25 +- .../include-cached-directive.js | 26 +- .../include-cached/include-cached-spec.js | 19 +- .../stf/common-ui/include-cached/index.js | 4 +- res/app/components/stf/common-ui/index.js | 42 +- .../add-adb-key-modal-service.js | 60 +- .../add-adb-key-modal-spec.js | 10 +- .../modals/add-adb-key-modal/index.js | 4 +- .../stf/common-ui/modals/common/index.js | 2 +- .../external-url-modal-service.js | 66 +- .../external-url-modal-spec.js | 10 +- .../modals/external-url-modal/index.js | 6 +- .../on-load-event-directive.js | 14 +- .../fatal-message/fatal-message-service.js | 140 +- .../fatal-message/fatal-message-spec.js | 10 +- .../common-ui/modals/fatal-message/index.js | 6 +- .../generic-modal/generic-modal-service.js | 48 +- .../generic-modal/generic-modal-spec.js | 10 +- .../common-ui/modals/generic-modal/index.js | 4 +- .../components/stf/common-ui/modals/index.js | 12 +- .../common-ui/modals/lightbox-image/index.js | 4 +- .../lightbox-image/lightbox-image-service.js | 58 +- .../lightbox-image/lightbox-image-spec.js | 10 +- .../common-ui/modals/save-log-modal/index.js | 6 +- .../modals/save-log-modal/save-log-service.js | 231 +- .../modals/save-log-modal/save-log-spec.js | 10 +- .../modals/socket-disconnected/index.js | 4 +- .../socket-disconnected-service.js | 51 +- .../socket-disconnected-spec.js | 10 +- .../modals/temporarily-unavailable/index.js | 4 +- .../temporarily-unavailable-service.js | 77 +- .../common-ui/modals/version-update/index.js | 6 +- .../version-update/version-update-service.js | 38 +- .../version-update/version-update-spec.js | 12 +- .../stf/common-ui/ng-enter/index.js | 2 +- .../common-ui/ng-enter/ng-enter-directive.js | 18 +- .../stf/common-ui/ng-enter/ng-enter-spec.js | 19 +- .../stf/common-ui/nice-tabs/index.js | 4 +- .../common-ui/nice-tabs/nice-tab-directive.js | 16 +- .../nice-tabs/nice-tabs-directive.js | 52 +- .../stf/common-ui/nice-tabs/nice-tabs-spec.js | 19 +- .../stf/common-ui/nothing-to-show/index.js | 2 +- .../nothing-to-show-directive.js | 24 +- .../stf/common-ui/notifications/index.js | 4 +- .../stf/common-ui/pagination/index.js | 6 +- .../pagination/pagination-directive.js | 34 +- .../common-ui/pagination/pagination-filter.js | 18 +- .../pagination/pagination-service.js | 26 +- .../stf/common-ui/refresh-page/index.js | 2 +- .../refresh-page/refresh-page-directive.js | 22 +- .../refresh-page/refresh-page-spec.js | 19 +- .../stf/common-ui/safe-apply/index.js | 37 +- .../stf/common-ui/stacked-icon/index.js | 2 +- .../stacked-icon/stacked-icon-directive.js | 18 +- .../components/stf/common-ui/table/index.js | 2 +- .../stf/common-ui/text-focus-select/index.js | 2 +- .../text-focus-select-directive.js | 16 +- .../text-focus-select-spec.js | 19 +- .../stf/common-ui/tooltips/index.js | 2 +- .../common-ui/tooltips/tooltips-directive.js | 8 +- .../stf/common-ui/tooltips/tooltips-spec.js | 19 +- .../components/stf/control/control-service.js | 618 +- res/app/components/stf/control/index.js | 8 +- .../device-context-menu-directive.js | 43 +- .../device-context-menu-spec.js | 19 +- .../stf/device-context-menu/index.js | 4 +- .../stf/device/device-info-filter/index.js | 246 +- .../components/stf/device/device-service.js | 436 +- .../enhance-device/enhance-device-service.js | 230 +- .../stf/device/enhance-device/index.js | 4 +- res/app/components/stf/device/index.js | 10 +- .../stf/device/state-classes-service.js | 68 +- .../components/stf/devices/devices-service.js | 196 +- res/app/components/stf/devices/index.js | 6 +- .../filter-string/filter-string-service.js | 65 +- .../stf/filter-string/filter-string-spec.js | 10 +- res/app/components/stf/filter-string/index.js | 2 +- .../components/stf/groups/groups-service.js | 332 +- res/app/components/stf/groups/index.js | 4 +- .../image-onload-animate-directive.js | 24 +- .../image-onload/image-onload-directive.js | 16 +- .../stf/image-onload/image-onload-spec.js | 19 +- res/app/components/stf/image-onload/index.js | 4 +- res/app/components/stf/install/index.js | 10 +- .../stf/install/install-error-filter.js | 184 +- .../components/stf/install/install-service.js | 259 +- .../components/stf/install/install-spec.js | 12 +- res/app/components/stf/keycodes/index.js | 6 +- .../stf/keycodes/keycodes-service.js | 150 +- .../components/stf/keycodes/keycodes-spec.js | 4 +- .../stf/keys/add-adb-key/adb-keys-service.js | 14 +- .../keys/add-adb-key/add-adb-key-directive.js | 88 +- .../stf/keys/add-adb-key/add-adb-key-spec.js | 19 +- .../components/stf/keys/add-adb-key/index.js | 8 +- res/app/components/stf/keys/index.js | 2 +- res/app/components/stf/landscape/index.js | 4 +- .../stf/landscape/landscape-directive.js | 91 +- .../stf/landscape/landscape-spec.js | 19 +- res/app/components/stf/language/index.js | 10 +- .../stf/language/language-provider.js | 28 +- .../stf/language/language-service.js | 62 +- res/app/components/stf/logcat-table/index.js | 2 +- .../logcat-table/logcat-table-directive.js | 344 +- .../stf/logcat-table/logcat-table-spec.js | 19 +- res/app/components/stf/logcat/index.js | 8 +- .../components/stf/logcat/logcat-service.js | 295 +- res/app/components/stf/logcat/logcat-spec.js | 10 +- res/app/components/stf/native-url/index.js | 2 +- .../stf/native-url/native-url-service.js | 121 +- .../stf/native-url/native-url-spec.js | 10 +- res/app/components/stf/nav-menu/index.js | 2 +- .../stf/nav-menu/nav-menu-directive.js | 102 +- .../components/stf/nav-menu/nav-menu-spec.js | 19 +- .../components/stf/page-visibility/index.js | 2 +- .../page-visibility-service.js | 26 +- .../components/stf/port-forwarding/index.js | 2 +- .../port-forwarding-service.js | 4 +- .../port-forwarding/port-forwarding-spec.js | 10 +- .../components/stf/scoped-hotkeys/index.js | 4 +- .../scoped-hotkeys/scoped-hotkeys-service.js | 45 +- .../stf/scoped-hotkeys/scoped-hotkeys-spec.js | 10 +- .../screen/fast-image-render/canvas-render.js | 40 +- .../stf/screen/fast-image-render/index.js | 268 +- .../test/performance_test.js | 59 +- .../screen/fast-image-render/webgl-render.js | 409 +- res/app/components/stf/screen/imagepool.js | 28 +- res/app/components/stf/screen/index.js | 18 +- res/app/components/stf/screen/rotator-test.js | 110 +- res/app/components/stf/screen/rotator.js | 54 +- .../components/stf/screen/scaling/index.js | 2 +- .../stf/screen/scaling/scaling-service.js | 354 +- .../stf/screen/screen-controller.js | 18 +- .../components/stf/screen/screen-directive.js | 1972 +-- .../stf/screen/screen-keyboard/index.js | 2 +- .../screen-keyboard-directive.js | 13 +- .../screen-keyboard/screen-keyboard-spec.js | 19 +- .../stf/screen/screen-loader/index.js | 4 +- .../screen-loader/screen-loader-directive.js | 22 +- .../screen-loader/screen-loader-service.js | 58 +- .../stf/screen/screen-touch/index.js | 2 +- .../screen-touch/screen-touch-directive.js | 8 +- .../screen/screen-touch/screen-touch-spec.js | 19 +- res/app/components/stf/settings/index.js | 6 +- .../stf/settings/settings-service.js | 244 +- res/app/components/stf/socket/index.js | 10 +- .../components/stf/socket/socket-service.js | 78 +- .../stf/socket/socket-state/index.js | 49 +- .../socket-state/socket-state-directive.js | 174 +- res/app/components/stf/standalone/index.js | 26 +- .../stf/standalone/standalone-directive.js | 14 +- .../stf/standalone/standalone-service.js | 185 +- res/app/components/stf/storage/index.js | 4 +- .../components/stf/storage/storage-service.js | 76 +- res/app/components/stf/text-history/index.js | 2 +- .../text-history/text-history-directive.js | 12 +- .../stf/text-history/text-history-spec.js | 19 +- res/app/components/stf/timeline/index.js | 2 +- .../stf/timeline/timeline-service.js | 72 +- .../components/stf/timeline/timeline-spec.js | 10 +- res/app/components/stf/timelines/index.js | 2 +- .../stf/timelines/timeline-message/index.js | 2 +- .../timeline-message-directive.js | 72 +- .../timeline-message/timeline-message-spec.js | 19 +- .../stf/timelines/timelines-directive.js | 18 +- .../stf/timelines/timelines-spec.js | 19 +- .../stf/tokens/access-token-service.js | 52 +- .../generate-access-token-directive.js | 40 +- .../stf/tokens/generate-access-token/index.js | 2 +- res/app/components/stf/tokens/index.js | 4 +- res/app/components/stf/transaction/index.js | 6 +- .../stf/transaction/transaction-error.js | 6 +- .../stf/transaction/transaction-service.js | 436 +- res/app/components/stf/upload/index.js | 4 +- .../stf/upload/upload-error-filter.js | 16 +- res/app/components/stf/upload/upload-spec.js | 12 +- .../stf/user/group/group-service.js | 100 +- res/app/components/stf/user/group/index.js | 8 +- res/app/components/stf/user/index.js | 8 +- res/app/components/stf/user/user-service.js | 88 +- res/app/components/stf/users/index.js | 4 +- res/app/components/stf/users/users-service.js | 188 +- .../stf/util/common/common-service.js | 382 +- res/app/components/stf/util/common/index.js | 4 +- res/app/components/stf/util/vendor/index.js | 2 +- .../components/stf/util/vendor/vendor-util.js | 20 +- .../activity/activity-controller.js | 53 +- .../control-panes/activity/activity-spec.js | 21 +- res/app/control-panes/activity/index.js | 18 +- .../control-panes/advanced/advanced-spec.js | 21 +- res/app/control-panes/advanced/index.js | 28 +- res/app/control-panes/advanced/input/index.js | 12 +- .../advanced/input/input-controller.js | 103 +- .../advanced/input/input-spec.js | 21 +- .../advanced/maintenance/index.js | 14 +- .../maintenance/maintenance-controller.js | 30 +- .../advanced/maintenance/maintenance-spec.js | 21 +- .../advanced/port-forwarding/index.js | 16 +- .../port-forwarding-controller.js | 100 +- .../port-forwarding/port-forwarding-spec.js | 21 +- .../control-panes/advanced/run-js/index.js | 12 +- .../advanced/run-js/run-js-spec.js | 21 +- res/app/control-panes/advanced/usb/index.js | 12 +- .../control-panes/advanced/usb/usb-spec.js | 21 +- res/app/control-panes/advanced/vnc/index.js | 16 +- .../advanced/vnc/vnc-controller.js | 14 +- .../control-panes/advanced/vnc/vnc-spec.js | 21 +- .../control-panes/control-panes-controller.js | 160 +- .../control-panes-hotkeys-controller.js | 195 +- .../control-panes-no-device-controller.js | 13 +- .../control-panes/control-panes-service.js | 47 +- res/app/control-panes/cpu/cpu-spec.js | 21 +- res/app/control-panes/cpu/index.js | 12 +- .../dashboard/apps/apps-controller.js | 154 +- res/app/control-panes/dashboard/apps/index.js | 16 +- .../dashboard/booking/booking-controller.js | 74 +- .../control-panes/dashboard/booking/index.js | 20 +- .../dashboard/clipboard/clipboard-spec.js | 21 +- .../dashboard/clipboard/index.js | 16 +- .../control-panes/dashboard/dashboard-spec.js | 21 +- res/app/control-panes/dashboard/index.js | 28 +- .../activities/activities-controller.js | 165 +- .../install/activities/activities-spec.js | 21 +- .../dashboard/install/activities/index.js | 16 +- .../control-panes/dashboard/install/index.js | 24 +- .../dashboard/install/install-controller.js | 56 +- .../dashboard/install/install-spec.js | 21 +- .../dashboard/navigation/index.js | 12 +- .../navigation/navigation-controller.js | 111 +- .../dashboard/navigation/navigation-spec.js | 21 +- .../dashboard/remote-debug/index.js | 16 +- .../remote-debug/remote-debug-controller.js | 63 +- .../remote-debug/remote-debug-spec.js | 21 +- .../control-panes/dashboard/shell/index.js | 16 +- .../dashboard/shell/shell-controller.js | 48 +- .../dashboard/shell/shell-spec.js | 28 +- .../device-control-controller.js | 354 +- .../device-control-key-directive.js | 74 +- res/app/control-panes/device-control/index.js | 32 +- .../explorer/explorer-controller.js | 112 +- .../control-panes/explorer/explorer-spec.js | 21 +- res/app/control-panes/explorer/index.js | 121 +- res/app/control-panes/index.js | 104 +- res/app/control-panes/info/index.js | 16 +- res/app/control-panes/info/info-controller.js | 41 +- res/app/control-panes/info/info-spec.js | 21 +- res/app/control-panes/inspect/index.js | 12 +- res/app/control-panes/inspect/inspect-spec.js | 21 +- res/app/control-panes/logs/index.js | 16 +- res/app/control-panes/logs/logs-controller.js | 363 +- res/app/control-panes/logs/logs-spec.js | 31 +- .../control-panes/performance/cpu/cpu-spec.js | 21 +- .../control-panes/performance/cpu/index.js | 14 +- res/app/control-panes/performance/index.js | 14 +- .../performance/performance-spec.js | 21 +- res/app/control-panes/resources/index.js | 12 +- .../control-panes/resources/resources-spec.js | 21 +- .../column/device-column-service.js | 1536 +- res/app/device-list/column/index.js | 8 +- .../details/device-list-details-directive.js | 1506 +- res/app/device-list/details/index.js | 14 +- res/app/device-list/device-list-controller.js | 546 +- res/app/device-list/device-list.css | 1 + .../empty/device-list-empty-directive.js | 54 +- res/app/device-list/empty/index.js | 2 +- .../icons/device-list-icons-directive.js | 1129 +- res/app/device-list/icons/index.js | 14 +- res/app/device-list/index.js | 48 +- .../stats/device-list-stats-directive.js | 190 +- res/app/device-list/stats/index.js | 4 +- res/app/device-list/util/patch-array/index.js | 98 +- .../util/patch-array/patch-array-test.js | 222 +- .../device-list/util/query-parser/index.js | 198 +- .../util/query-parser/query-parser-test.js | 245 +- res/app/docs/docs-controller.js | 6 +- res/app/docs/index.js | 55 +- res/app/group-list/group-list-controller.js | 785 +- res/app/group-list/index.js | 54 +- res/app/layout/index.js | 30 +- res/app/layout/layout-controller.js | 22 +- res/app/menu/index.js | 26 +- res/app/menu/menu-controller.js | 201 +- res/app/menu/menu-spec.js | 23 +- .../settings/devices/devices-controller.js | 355 +- res/app/settings/devices/devices-spec.js | 21 +- res/app/settings/devices/index.js | 20 +- .../alert-message/alert-message-controller.js | 39 +- .../settings/general/alert-message/index.js | 16 +- .../date-format/date-format-controller.js | 37 +- res/app/settings/general/date-format/index.js | 14 +- .../email-address-separator-controller.js | 37 +- .../general/email-address-separator/index.js | 16 +- .../settings/general/general-controller.js | 12 +- res/app/settings/general/general-spec.js | 21 +- res/app/settings/general/index.js | 26 +- res/app/settings/general/language/index.js | 16 +- .../general/language/language-controller.js | 12 +- res/app/settings/general/local/index.js | 20 +- .../local/local-settings-controller.js | 9 +- .../filters/available-objects-filter.js | 18 +- .../groups/filters/group-objects-filter.js | 18 +- res/app/settings/groups/groups-controller.js | 1612 +- res/app/settings/groups/groups-spec.js | 21 +- res/app/settings/groups/groups.pug | 2 +- res/app/settings/groups/index.js | 54 +- res/app/settings/index.js | 60 +- .../access-tokens/access-tokens-controller.js | 21 +- .../keys/access-tokens/access-tokens-spec.js | 21 +- res/app/settings/keys/access-tokens/index.js | 20 +- .../keys/adb-keys/adb-keys-controller.js | 34 +- .../keys/adb-keys/adb-keys-service.js | 14 +- .../settings/keys/adb-keys/adb-keys-spec.js | 21 +- res/app/settings/keys/adb-keys/index.js | 18 +- res/app/settings/keys/index.js | 16 +- res/app/settings/keys/keys-spec.js | 21 +- res/app/settings/notifications/index.js | 18 +- .../notifications/notifications-service.js | 4 +- res/app/settings/settings-controller.js | 79 +- res/app/settings/shell/index.js | 16 +- res/app/settings/shell/shell-controller.js | 143 +- res/app/settings/users/index.js | 20 +- res/app/settings/users/users-controller.js | 460 +- res/app/settings/users/users-spec.js | 21 +- res/app/user/index.js | 15 +- res/auth/ldap/scripts/entry.js | 33 +- res/auth/ldap/scripts/signin/index.js | 18 +- .../ldap/scripts/signin/signin-controller.js | 75 +- res/auth/mock/scripts/entry.js | 32 +- res/auth/mock/scripts/signin/index.js | 18 +- .../mock/scripts/signin/signin-controller.js | 75 +- res/common/lang/index.js | 16 +- res/common/lang/translations/stf.ru_RU.json | 5 +- res/common/status/webpack.config.js | 12 +- res/test/e2e/control/control-spec.js | 233 +- res/test/e2e/devices/devices-spec.js | 183 +- res/test/e2e/devices/index.js | 109 +- res/test/e2e/help/help-spec.js | 10 +- res/test/e2e/helpers/browser-logs.js | 47 +- res/test/e2e/helpers/fail-fast.js | 10 +- res/test/e2e/helpers/gulp-protractor-adv.js | 319 +- res/test/e2e/helpers/wait-url.js | 18 +- res/test/e2e/login/index.js | 124 +- res/test/e2e/login/login-spec.js | 32 +- res/test/e2e/settings/settings-spec.js | 10 +- res/test/e2e/widget-container/index.js | 25 +- .../widget-container/widget-container-spec.js | 49 +- res/test/karma.conf.js | 114 +- res/test/protractor-appium.conf.js | 10 +- res/test/protractor-multi.conf.js | 32 +- res/test/protractor.conf.js | 168 +- res/web_modules/angular-borderlayout/index.js | 2 +- res/web_modules/angular-growl/index.js | 4 +- res/web_modules/angular-hotkeys/index.js | 4 +- res/web_modules/angular-ladda/index.js | 6 +- res/web_modules/angular-xeditable/index.js | 2 +- res/web_modules/epoch/index.js | 2 +- res/web_modules/gettext/index.js | 2 +- res/web_modules/ng-context-menu/index.js | 2 +- res/web_modules/ng-file-upload/index.js | 2 +- res/web_modules/ui-bootstrap/index.js | 2 +- scripts/mongo_setup.sh | 8 +- scripts/variables.env | 2 +- test/api/.dockerignore | 1 + test/api/.gitignore | 3 + test/api/conftest.py | 81 + test/api/justfile | 10 + test/api/openapi-gen-config.yaml | 2 + test/api/poetry.lock | 814 + test/api/pyproject.toml | 24 + .../smartphone-test-farm-client/.gitignore | 23 + .../api/smartphone-test-farm-client/README.md | 124 + .../pyproject.toml | 27 + .../smartphone_test_farm_client/__init__.py | 8 + .../api/__init__.py | 1 + .../api/admin/__init__.py | 0 .../api/admin/add_origin_group_device.py | 171 + .../api/admin/add_origin_group_devices.py | 201 + .../api/admin/add_user_device_v3.py | 196 + .../api/admin/create_service_user.py | 209 + .../api/admin/create_user.py | 175 + .../api/admin/create_user_access_token.py | 179 + .../api/admin/delete_device.py | 224 + .../api/admin/delete_devices.py | 233 + .../api/admin/delete_user.py | 179 + .../api/admin/delete_user_access_token.py | 171 + .../api/admin/delete_user_access_tokens.py | 158 + .../api/admin/delete_user_device.py | 175 + .../api/admin/delete_users.py | 188 + .../api/admin/get_device_groups.py | 179 + .../api/admin/get_user_access_token.py | 171 + .../api/admin/get_user_access_tokens_v2.py | 158 + .../api/admin/get_user_device.py | 192 + .../api/admin/get_user_devices_v2.py | 179 + .../api/admin/grant_admin.py | 162 + .../api/admin/put_device_by_serial.py | 181 + .../api/admin/remote_connect_user_device.py | 175 + .../admin/remote_disconnect_user_device.py | 171 + .../api/admin/remove_origin_group_device.py | 175 + .../api/admin/remove_origin_group_devices.py | 205 + .../api/admin/renew_adb_port.py | 158 + .../api/admin/revoke_admin.py | 162 + .../update_default_user_groups_quotas.py | 192 + .../api/admin/update_storage_info.py | 209 + .../api/admin/update_user_groups_quotas.py | 205 + .../api/admin/update_users_alert_message.py | 168 + .../api/autotests/__init__.py | 0 .../api/autotests/capture_devices.py | 286 + .../api/autotests/free_devices.py | 166 + .../api/autotests/install_on_device.py | 181 + .../api/autotests/use_and_connect_device.py | 168 + .../api/devices/__init__.py | 0 .../api/devices/get_adb_range.py | 134 + .../api/devices/get_device_bookings.py | 179 + .../api/devices/get_device_by_serial.py | 179 + .../api/devices/get_devices.py | 186 + .../api/groups/__init__.py | 0 .../api/groups/add_group_device.py | 176 + .../api/groups/add_group_devices.py | 186 + .../api/groups/add_group_user.py | 167 + .../api/groups/add_group_users.py | 177 + .../api/groups/create_group.py | 164 + .../api/groups/delete_group.py | 158 + .../api/groups/delete_groups.py | 168 + .../api/groups/get_group.py | 175 + .../api/groups/get_group_device.py | 192 + .../api/groups/get_group_devices.py | 194 + .../api/groups/get_group_user.py | 192 + .../api/groups/get_group_users.py | 183 + .../api/groups/get_groups.py | 181 + .../api/groups/remove_group_device.py | 167 + .../api/groups/remove_group_devices.py | 177 + .../api/groups/remove_group_user.py | 167 + .../api/groups/remove_group_users.py | 177 + .../api/groups/update_group.py | 186 + .../api/stats/__init__.py | 0 .../api/stats/write_stats.py | 194 + .../api/user/__init__.py | 0 .../api/user/add_adb_public_key.py | 107 + .../api/user/add_user_device.py | 107 + .../api/user/add_user_device_v2.py | 183 + .../api/user/create_access_token.py | 166 + .../api/user/delete_access_token.py | 158 + .../api/user/delete_access_tokens.py | 134 + .../api/user/delete_user_device_by_serial.py | 162 + .../api/user/get_access_token.py | 158 + .../api/user/get_access_tokens.py | 134 + .../api/user/get_device_owner.py | 154 + .../api/user/get_device_size.py | 154 + .../api/user/get_device_type.py | 154 + .../api/user/get_user.py | 130 + .../api/user/get_user_access_tokens.py | 134 + .../api/user/get_user_device_by_serial.py | 179 + .../api/user/get_user_devices.py | 166 + .../remote_connect_user_device_by_serial.py | 158 + ...remote_disconnect_user_device_by_serial.py | 158 + .../api/user/remove_adb_public_key.py | 107 + .../api/users/__init__.py | 0 .../api/users/get_user_by_email.py | 179 + .../api/users/get_users.py | 170 + .../api/users/get_users_alert_message.py | 170 + .../smartphone_test_farm_client/client.py | 268 + .../smartphone_test_farm_client/errors.py | 16 + .../models/__init__.py | 135 + .../models/access_tokens_response.py | 74 + .../models/adb_install_flags_payload.py | 72 + .../models/add_adb_public_key_body.py | 69 + .../models/add_user_device_payload.py | 71 + .../models/alert_message.py | 76 + .../models/alert_message_payload.py | 93 + .../alert_message_payload_activation.py | 9 + .../models/alert_message_payload_level.py | 10 + .../models/alert_message_response.py | 80 + .../models/auto_test_response.py | 90 + .../models/auto_test_response_group.py | 74 + .../auto_test_response_group_devices_item.py | 43 + .../models/conflict.py | 109 + .../models/conflict_date.py | 84 + .../models/conflict_owner.py | 68 + .../models/conflicts_response.py | 90 + .../models/default_response.py | 66 + .../models/device.py | 610 + .../models/device_battery.py | 112 + .../models/device_browser.py | 83 + .../models/device_browser_apps_item.py | 94 + .../models/device_display.py | 148 + .../models/device_group.py | 172 + .../models/device_group_life_time.py | 83 + .../models/device_group_owner.py | 67 + .../models/device_list_response.py | 88 + .../models/device_network.py | 94 + .../models/device_owner_type_0.py | 76 + .../models/device_payload.py | 76 + .../models/device_payload_status.py | 8 + .../models/device_phone.py | 138 + .../models/device_provider.py | 67 + .../models/device_response.py | 80 + .../models/device_reverse_forwards_item.py | 85 + .../models/device_service.py | 67 + .../models/devices_payload.py | 59 + .../models/error_response.py | 58 + .../models/finger_print_payload.py | 59 + .../models/get_devices_target.py | 12 + .../models/group_list_response.py | 88 + .../models/group_list_response_groups_item.py | 43 + .../models/group_payload.py | 140 + .../models/group_payload_class.py | 18 + .../models/group_payload_state.py | 9 + .../models/group_response.py | 80 + .../models/group_response_group.py | 43 + .../models/groups_payload.py | 59 + .../models/owner_response.py | 66 + .../remote_connect_user_device_response.py | 74 + .../models/service_user_response.py | 80 + ...service_user_response_service_user_info.py | 43 + .../models/size_response.py | 66 + .../models/token.py | 67 + .../models/type_response.py | 58 + .../models/unexpected_error_response.py | 66 + .../models/use_and_connect_device_body.py | 69 + .../models/user_access_token_response.py | 80 + .../models/user_access_tokens_response.py | 88 + .../models/user_list_response.py | 88 + .../models/user_list_response_users_item.py | 43 + .../models/user_response.py | 80 + .../models/user_response_user.py | 43 + .../models/users_payload.py | 59 + .../models/write_stats_data_body.py | 66 + .../models/write_stats_files_body.py | 84 + .../smartphone_test_farm_client/py.typed | 1 + .../smartphone_test_farm_client/types.py | 45 + test/api/test_autotest.py | 133 + test/api/test_groups.py | 9 + test/api/test_users.py | 150 + ui/.browserslistrc | 9 + ui/.dockerignore | 1 + ui/.env.development | 2 + ui/.gitignore | 28 + ui/.prettierignore | 3 + ui/.prettierrc | 12 + ui/.stylelintignore | 11 + ui/.stylelintrc | 23 + ui/README.md | 1 + ui/eslint.config.mjs | 361 + ui/index.html | 17 + ui/orval.config.ts | 11 + ui/package-lock.json | 14676 ++++++++++++++++ ui/package.json | 99 + ui/plop-templates/component.module.css.hbs | 1 + ui/plop-templates/component.tsx.hbs | 7 + ui/plop-templates/index.ts.hbs | 1 + ui/plopfile.mjs | 35 + ui/postcss.config.mjs | 5 + ui/public/locales/en/translation.json | 294 + ui/public/locales/es/translation.json | 294 + ui/public/locales/fr/translation.json | 377 + ui/public/locales/ja/translation.json | 333 + ui/public/locales/kk-KZ/translation.json | 309 + ui/public/locales/ko/translation.json | 344 + ui/public/locales/pl/translation.json | 205 + ui/public/locales/pt-BR/translation.json | 377 + ui/public/locales/ru-RU/translation.json | 310 + ui/public/locales/tt-RU/translation.json | 309 + ui/public/locales/zh-CN/translation.json | 373 + ui/public/locales/zh-Hant/translation.json | 276 + ui/public/mockServiceWorker.js | 295 + ui/public/vite.svg | 1 + ui/src/__mocks__/browser.ts | 5 + ui/src/__mocks__/enable-mocking.ts | 7 + ui/src/__mocks__/handlers.ts | 21 + .../responses/device-list-response.ts | 714 + ui/src/__mocks__/responses/user-response.ts | 232 + ui/src/api/openstf-api/index.ts | 17 + ui/src/api/openstf-api/openstf-api-client.ts | 6 + ui/src/api/openstf-api/routes.ts | 4 + ui/src/api/openstf/index.ts | 17 + ui/src/api/openstf/openstf-client.ts | 5 + ui/src/api/openstf/routes.ts | 4 + ui/src/api/openstf/types.ts | 9 + ui/src/api/socket.ts | 8 + ui/src/assets/device-hub.svg | 5 + ui/src/components/app/app-router.tsx | 34 + ui/src/components/app/app.tsx | 10 + ui/src/components/layouts/main-layout.tsx | 12 + .../conditional-render.test.tsx | 27 + .../conditional-render/conditional-render.tsx | 12 + .../lib/conditional-render/index.ts | 1 + ui/src/components/lib/statistic-card/index.ts | 1 + .../statistic-card/statistic-card.module.css | 9 + .../lib/statistic-card/statistic-card.tsx | 59 + ui/src/components/lib/statistic-card/types.ts | 6 + .../device-statistics.module.css | 7 + .../device-statistics/device-statistics.tsx | 45 + .../components/ui/device-statistics/index.ts | 1 + .../device-table/cells/booked-before-cell.tsx | 27 + .../cells/browser-cell/assets/amazon-silk.png | Bin 0 -> 3091 bytes .../browser-cell/assets/android-browser.png | Bin 0 -> 3358 bytes .../browser-cell/assets/asus-browser.png | Bin 0 -> 2080 bytes .../cells/browser-cell/assets/baidu.png | Bin 0 -> 2425 bytes .../cells/browser-cell/assets/chrome-beta.png | Bin 0 -> 2601 bytes .../browser-cell/assets/chrome-canary.png | Bin 0 -> 2600 bytes .../cells/browser-cell/assets/chrome-dev.png | Bin 0 -> 2684 bytes .../cells/browser-cell/assets/chrome.png | Bin 0 -> 2388 bytes .../cells/browser-cell/assets/explore.png | Bin 0 -> 3081 bytes .../browser-cell/assets/firefox-beta.png | Bin 0 -> 3298 bytes .../cells/browser-cell/assets/firefox.png | Bin 0 -> 3118 bytes .../browser-cell/assets/fujitsu-fbrowser.png | Bin 0 -> 2891 bytes .../assets/google-android-browser.png | Bin 0 -> 3479 bytes .../browser-cell/assets/htc-sense-browser.png | Bin 0 -> 2895 bytes .../browser-cell/assets/lenovo-browser.png | Bin 0 -> 2945 bytes .../cells/browser-cell/assets/maxthon.png | Bin 0 -> 1259 bytes .../assets/netstar-familysmile.png | Bin 0 -> 2401 bytes .../cells/browser-cell/assets/one.png | Bin 0 -> 2823 bytes .../cells/browser-cell/assets/opera-beta.png | Bin 0 -> 1514 bytes .../browser-cell/assets/opera-mini-native.png | Bin 0 -> 2729 bytes .../cells/browser-cell/assets/opera-mini.png | Bin 0 -> 2076 bytes .../cells/browser-cell/assets/opera.png | Bin 0 -> 2331 bytes .../assets/oppo-baidu-searchbox.png | Bin 0 -> 2479 bytes .../cells/browser-cell/assets/puffin-free.png | Bin 0 -> 3105 bytes .../assets/samsung-popupbrowser.png | Bin 0 -> 1978 bytes .../browser-cell/assets/samsung-sbrowser.png | Bin 0 -> 3302 bytes .../assets/softbank-parentalcontrols.png | Bin 0 -> 2156 bytes .../cells/browser-cell/assets/uc-mini.png | Bin 0 -> 2554 bytes .../cells/browser-cell/assets/uc.png | Bin 0 -> 2063 bytes .../browser-cell/assets/yahoo-ybrowser.png | Bin 0 -> 3104 bytes .../cells/browser-cell/assets/yahoo-yjtop.png | Bin 0 -> 1310 bytes .../cells/browser-cell/browser-cell.tsx | 116 + .../device-table/cells/device-status-cell.tsx | 108 + .../components/ui/device-table/cells/index.ts | 8 + .../ui/device-table/cells/link-cell.tsx | 16 + .../cells/model-cell/model-cell.module.css | 9 + .../cells/model-cell/model-cell.tsx | 32 + .../ui/device-table/cells/notes-cell.tsx | 52 + .../ui/device-table/cells/product-cell.tsx | 25 + .../cells/text-cell/text-cell.module.css | 4 + .../cells/text-cell/text-cell.tsx | 24 + ui/src/components/ui/device-table/columns.tsx | 400 + .../components/ui/device-table/constants.ts | 91 + .../ui/device-table/device-table.module.css | 102 + .../ui/device-table/device-table.tsx | 145 + .../device-table/header-with-translation.tsx | 8 + ui/src/components/ui/device-table/helpers.tsx | 96 + ui/src/components/ui/device-table/index.ts | 1 + .../components/ui/device-table/table-body.tsx | 52 + ui/src/components/ui/device-table/types.ts | 43 + ui/src/components/ui/header/header.module.css | 47 + ui/src/components/ui/header/header.tsx | 76 + ui/src/components/ui/header/index.ts | 1 + ui/src/components/ui/lang-switcher/index.ts | 1 + .../ui/lang-switcher/lang-switcher.module.css | 3 + .../ui/lang-switcher/lang-switcher.tsx | 55 + ui/src/components/ui/search-device/index.ts | 1 + .../ui/search-device/search-device.module.css | 9 + .../ui/search-device/search-device.tsx | 57 + .../ui/table-column-visibility/index.ts | 1 + .../table-column-visibility.module.css | 21 + .../table-column-visibility.tsx | 98 + .../views/control-page/control-page.async.tsx | 5 + .../views/control-page/control-page.tsx | 1 + ui/src/components/views/control-page/index.ts | 1 + .../views/devices-page/devices-page.async.tsx | 5 + .../views/devices-page/devices-page.tsx | 45 + ui/src/components/views/devices-page/index.ts | 1 + .../views/groups-page/groups-page.async.tsx | 5 + .../views/groups-page/groups-page.tsx | 1 + ui/src/components/views/groups-page/index.ts | 1 + .../components/views/settings-page/index.ts | 1 + .../settings-page/settings-page.async.tsx | 5 + .../settings-page/settings-page.module.css | 12 + .../views/settings-page/settings-page.tsx | 110 + .../tabs/general-settings-tab.tsx | 11 + ui/src/config/i18n/i18n.ts | 34 + ui/src/config/queries/query-client.ts | 11 + ui/src/config/queries/query-key-store.ts | 29 + ui/src/constants/route-paths.ts | 10 + .../generated/types/accessTokensResponse.ts | 13 + .../generated/types/adbInstallFlagsPayload.ts | 15 + ui/src/generated/types/addAdbPublicKeyBody.ts | 14 + .../types/addOriginGroupDevicesParams.ts | 14 + .../generated/types/addUserDevicePayload.ts | 17 + .../generated/types/addUserDeviceV2Params.ts | 14 + .../generated/types/addUserDeviceV3Params.ts | 14 + ui/src/generated/types/alertMessage.ts | 16 + ui/src/generated/types/alertMessagePayload.ts | 21 + .../types/alertMessagePayloadActivation.ts | 19 + .../types/alertMessagePayloadLevel.ts | 19 + .../generated/types/alertMessageResponse.ts | 14 + ui/src/generated/types/autoTestResponse.ts | 14 + .../generated/types/autoTestResponseGroup.ts | 12 + .../types/autoTestResponseGroupDevicesItem.ts | 9 + .../generated/types/captureDevicesParams.ts | 46 + ui/src/generated/types/conflict.ts | 20 + ui/src/generated/types/conflictDate.ts | 15 + ui/src/generated/types/conflictOwner.ts | 15 + ui/src/generated/types/conflictsResponse.ts | 18 + .../types/createAccessTokenParams.ts | 14 + .../types/createServiceUserParams.ts | 22 + .../types/createUserAccessTokenParams.ts | 14 + ui/src/generated/types/createUserParams.ts | 14 + ui/src/generated/types/defaultResponse.ts | 12 + ui/src/generated/types/deleteDeviceParams.ts | 26 + ui/src/generated/types/deleteDevicesParams.ts | 26 + ui/src/generated/types/deleteUserParams.ts | 14 + ui/src/generated/types/deleteUsersParams.ts | 14 + ui/src/generated/types/device.ts | 68 + ui/src/generated/types/deviceBattery.ts | 17 + ui/src/generated/types/deviceBrowser.ts | 13 + .../generated/types/deviceBrowserAppsItem.ts | 15 + ui/src/generated/types/deviceBrowserPhone.ts | 19 + .../generated/types/deviceBrowserService.ts | 12 + ui/src/generated/types/deviceDisplay.ts | 21 + ui/src/generated/types/deviceGroup.ts | 23 + ui/src/generated/types/deviceGroupLifeTime.ts | 12 + ui/src/generated/types/deviceGroupOwner.ts | 12 + ui/src/generated/types/deviceListResponse.ts | 14 + ui/src/generated/types/deviceNetwork.ts | 15 + ui/src/generated/types/deviceOwner.ts | 16 + ui/src/generated/types/devicePayload.ts | 18 + ui/src/generated/types/devicePayloadStatus.ts | 17 + ui/src/generated/types/devicePhone.ts | 19 + ui/src/generated/types/deviceProvider.ts | 12 + ui/src/generated/types/deviceResponse.ts | 14 + .../types/deviceReverseForwardsItem.ts | 16 + ui/src/generated/types/deviceService.ts | 12 + ui/src/generated/types/devicesPayload.ts | 15 + ui/src/generated/types/errorResponse.ts | 11 + ui/src/generated/types/fingerPrintPayload.ts | 15 + ui/src/generated/types/freeDevicesParams.ts | 14 + .../types/getDeviceBookingsParams.ts | 14 + .../types/getDeviceBySerialParams.ts | 14 + .../generated/types/getDeviceGroupsParams.ts | 14 + ui/src/generated/types/getDevicesParams.ts | 25 + ui/src/generated/types/getDevicesTarget.ts | 18 + .../generated/types/getGroupDeviceParams.ts | 14 + .../generated/types/getGroupDevicesParams.ts | 18 + ui/src/generated/types/getGroupParams.ts | 14 + ui/src/generated/types/getGroupUserParams.ts | 14 + ui/src/generated/types/getGroupUsersParams.ts | 14 + ui/src/generated/types/getGroupsParams.ts | 18 + .../generated/types/getUserByEmailParams.ts | 14 + .../types/getUserDeviceBySerialParams.ts | 14 + ui/src/generated/types/getUserDeviceParams.ts | 14 + .../generated/types/getUserDevicesParams.ts | 14 + .../generated/types/getUserDevicesV2Params.ts | 14 + .../types/getUsersAlertMessageParams.ts | 14 + ui/src/generated/types/getUsersParams.ts | 14 + ui/src/generated/types/groupListResponse.ts | 14 + .../types/groupListResponseGroupsItem.ts | 9 + ui/src/generated/types/groupPayload.ts | 33 + ui/src/generated/types/groupPayloadClass.ts | 27 + ui/src/generated/types/groupPayloadState.ts | 18 + ui/src/generated/types/groupResponse.ts | 15 + ui/src/generated/types/groupResponseGroup.ts | 12 + ui/src/generated/types/groupsPayload.ts | 15 + ui/src/generated/types/index.ts | 120 + ui/src/generated/types/ownerResponse.ts | 12 + .../types/remoteConnectUserDeviceResponse.ts | 13 + .../types/removeOriginGroupDevicesParams.ts | 14 + ui/src/generated/types/response.ts | 12 + ui/src/generated/types/serviceUserResponse.ts | 14 + .../serviceUserResponseServiceUserInfo.ts | 9 + ui/src/generated/types/sizeResponse.ts | 12 + ui/src/generated/types/token.ts | 12 + ui/src/generated/types/typeResponse.ts | 11 + .../types/unexpectedErrorResponse.ts | 12 + .../updateDefaultUserGroupsQuotasParams.ts | 22 + .../types/updateStorageInfoParams.ts | 22 + .../types/updateUserGroupsQuotasParams.ts | 22 + .../types/useAndConnectDeviceBody.ts | 14 + .../types/userAccessTokenResponse.ts | 14 + .../types/userAccessTokensResponse.ts | 14 + ui/src/generated/types/userListResponse.ts | 14 + .../types/userListResponseUsersItem.ts | 9 + ui/src/generated/types/userResponse.ts | 14 + ui/src/generated/types/userResponseUser.ts | 24 + .../generated/types/userResponseUserGroups.ts | 14 + .../types/userResponseUserGroupsQuotas.ts | 18 + .../userResponseUserGroupsQuotasAllocated.ts | 12 + .../userResponseUserGroupsQuotasConsumed.ts | 12 + .../types/userResponseUserSettings.ts | 21 + ...sponseUserSettingsDeviceListColumnsItem.ts | 12 + .../userResponseUserSettingsDeviceListSort.ts | 14 + ...onseUserSettingsDeviceListSortFixedItem.ts | 12 + ...ponseUserSettingsDeviceListSortUserItem.ts | 12 + ...erResponseUserSettingsGroupItemsPerPage.ts | 12 + ui/src/generated/types/usersPayload.ts | 15 + ui/src/generated/types/writeStatsBodyOne.ts | 14 + ui/src/generated/types/writeStatsBodyTwo.ts | 14 + ui/src/lib/hooks/use-click-outside.hook.ts | 17 + ui/src/lib/hooks/use-debounce.hook.ts | 17 + ui/src/lib/hooks/use-get-auth-contact.hook.ts | 8 + ui/src/lib/hooks/use-get-auth-docs.hook.ts | 7 + .../lib/utils/capitalize-first-letter.util.ts | 1 + .../utils/date-to-formatted-string.util.ts | 7 + .../device-service-to-string.util.test.ts | 15 + .../utils/device-service-to-string.util.ts | 15 + .../generate-transaction-socket-channel.ts | 1 + ui/src/lib/utils/get-device-state.util.ts | 43 + ui/src/lib/utils/is-device-inactive.util.ts | 16 + .../lib/utils/is-device-usable.util.test.ts | 15 + ui/src/lib/utils/is-device-usable.util.ts | 16 + .../utils/resolve-table-filter-value.util.ts | 50 + .../resolve-table-fitler-value.util.test.ts | 150 + ui/src/main.tsx | 29 + ui/src/services/core/control-service.ts | 23 + .../transaction-service.ts | 64 + .../core/transaction-service/types.ts | 13 + ui/src/services/group-service.ts | 54 + ui/src/services/settings-service.ts | 13 + ui/src/store/current-user-profile-store.ts | 24 + ui/src/store/device-connection.ts | 43 + ui/src/store/device-list-store.ts | 81 + ui/src/store/device-table-state.ts | 32 + ui/src/store/mobx-query.ts | 73 + ui/src/styles/index.css | 11 + ui/src/styles/reset.css | 68 + ui/src/styles/variables/global.css | 7 + ui/src/types/array-type.type.ts | 1 + ui/src/types/column-group.type.ts | 8 + ui/src/types/device-change-message.type.ts | 4 + ui/src/types/enums/device-state.enum.ts | 12 + ui/src/vite-env.d.ts | 26 + ui/tsconfig.app.json | 34 + ui/tsconfig.json | 7 + ui/tsconfig.node.json | 22 + ui/vite.config.ts | 23 + ui/vitest-setup.ts | 8 + 1143 files changed, 80059 insertions(+), 30858 deletions(-) delete mode 100644 WebDriverAgent/WebDriverAgentLib/Utilities/FBBaseActionsParser.m create mode 100644 bin/package.json delete mode 100755 bin/stf create mode 100755 bin/stf.mjs create mode 100755 lib/cli/ios-device/install-wda.sh create mode 100755 lib/db/api.js delete mode 100644 lib/db/api.mjs create mode 100644 lib/db/index.js delete mode 100644 lib/db/index.mjs create mode 100755 lib/db/setup.js delete mode 100644 lib/db/setup.mjs create mode 100755 lib/db/tables.js delete mode 100644 lib/db/tables.mjs create mode 100644 lib/util/instrument.mjs create mode 100644 test/api/.dockerignore create mode 100644 test/api/.gitignore create mode 100644 test/api/conftest.py create mode 100644 test/api/justfile create mode 100755 test/api/openapi-gen-config.yaml create mode 100644 test/api/poetry.lock create mode 100644 test/api/pyproject.toml create mode 100644 test/api/smartphone-test-farm-client/.gitignore create mode 100644 test/api/smartphone-test-farm-client/README.md create mode 100644 test/api/smartphone-test-farm-client/pyproject.toml create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/__init__.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/__init__.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/__init__.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/add_origin_group_device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/add_origin_group_devices.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/add_user_device_v3.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/create_service_user.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/create_user.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/create_user_access_token.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/delete_device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/delete_devices.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/delete_user.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/delete_user_access_token.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/delete_user_access_tokens.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/delete_user_device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/delete_users.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/get_device_groups.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/get_user_access_token.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/get_user_access_tokens_v2.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/get_user_device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/get_user_devices_v2.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/grant_admin.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/put_device_by_serial.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/remote_connect_user_device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/remote_disconnect_user_device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/remove_origin_group_device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/remove_origin_group_devices.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/renew_adb_port.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/revoke_admin.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/update_default_user_groups_quotas.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/update_storage_info.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/update_user_groups_quotas.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/admin/update_users_alert_message.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/autotests/__init__.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/autotests/capture_devices.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/autotests/free_devices.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/autotests/install_on_device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/autotests/use_and_connect_device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/devices/__init__.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/devices/get_adb_range.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/devices/get_device_bookings.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/devices/get_device_by_serial.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/devices/get_devices.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/__init__.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/add_group_device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/add_group_devices.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/add_group_user.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/add_group_users.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/create_group.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/delete_group.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/delete_groups.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/get_group.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/get_group_device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/get_group_devices.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/get_group_user.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/get_group_users.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/get_groups.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/remove_group_device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/remove_group_devices.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/remove_group_user.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/remove_group_users.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/groups/update_group.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/stats/__init__.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/stats/write_stats.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/__init__.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/add_adb_public_key.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/add_user_device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/add_user_device_v2.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/create_access_token.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/delete_access_token.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/delete_access_tokens.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/delete_user_device_by_serial.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/get_access_token.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/get_access_tokens.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/get_device_owner.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/get_device_size.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/get_device_type.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/get_user.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/get_user_access_tokens.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/get_user_device_by_serial.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/get_user_devices.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/remote_connect_user_device_by_serial.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/remote_disconnect_user_device_by_serial.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/user/remove_adb_public_key.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/users/__init__.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/users/get_user_by_email.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/users/get_users.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/api/users/get_users_alert_message.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/client.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/errors.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/__init__.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/access_tokens_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/adb_install_flags_payload.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/add_adb_public_key_body.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/add_user_device_payload.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/alert_message.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/alert_message_payload.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/alert_message_payload_activation.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/alert_message_payload_level.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/alert_message_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/auto_test_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/auto_test_response_group.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/auto_test_response_group_devices_item.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/conflict.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/conflict_date.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/conflict_owner.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/conflicts_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/default_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_battery.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_browser.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_browser_apps_item.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_display.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_group.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_group_life_time.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_group_owner.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_list_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_network.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_owner_type_0.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_payload.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_payload_status.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_phone.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_provider.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_reverse_forwards_item.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/device_service.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/devices_payload.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/error_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/finger_print_payload.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/get_devices_target.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/group_list_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/group_list_response_groups_item.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/group_payload.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/group_payload_class.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/group_payload_state.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/group_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/group_response_group.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/groups_payload.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/owner_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/remote_connect_user_device_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/service_user_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/service_user_response_service_user_info.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/size_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/token.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/type_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/unexpected_error_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/use_and_connect_device_body.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/user_access_token_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/user_access_tokens_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/user_list_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/user_list_response_users_item.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/user_response.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/user_response_user.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/users_payload.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/write_stats_data_body.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/models/write_stats_files_body.py create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/py.typed create mode 100644 test/api/smartphone-test-farm-client/smartphone_test_farm_client/types.py create mode 100644 test/api/test_autotest.py create mode 100644 test/api/test_groups.py create mode 100644 test/api/test_users.py create mode 100644 ui/.browserslistrc create mode 100644 ui/.dockerignore create mode 100644 ui/.env.development create mode 100644 ui/.gitignore create mode 100644 ui/.prettierignore create mode 100644 ui/.prettierrc create mode 100644 ui/.stylelintignore create mode 100644 ui/.stylelintrc create mode 100644 ui/README.md create mode 100644 ui/eslint.config.mjs create mode 100644 ui/index.html create mode 100644 ui/orval.config.ts create mode 100644 ui/package-lock.json create mode 100644 ui/package.json create mode 100644 ui/plop-templates/component.module.css.hbs create mode 100644 ui/plop-templates/component.tsx.hbs create mode 100644 ui/plop-templates/index.ts.hbs create mode 100644 ui/plopfile.mjs create mode 100644 ui/postcss.config.mjs create mode 100644 ui/public/locales/en/translation.json create mode 100644 ui/public/locales/es/translation.json create mode 100644 ui/public/locales/fr/translation.json create mode 100644 ui/public/locales/ja/translation.json create mode 100644 ui/public/locales/kk-KZ/translation.json create mode 100644 ui/public/locales/ko/translation.json create mode 100644 ui/public/locales/pl/translation.json create mode 100644 ui/public/locales/pt-BR/translation.json create mode 100644 ui/public/locales/ru-RU/translation.json create mode 100644 ui/public/locales/tt-RU/translation.json create mode 100644 ui/public/locales/zh-CN/translation.json create mode 100644 ui/public/locales/zh-Hant/translation.json create mode 100644 ui/public/mockServiceWorker.js create mode 100644 ui/public/vite.svg create mode 100644 ui/src/__mocks__/browser.ts create mode 100644 ui/src/__mocks__/enable-mocking.ts create mode 100644 ui/src/__mocks__/handlers.ts create mode 100644 ui/src/__mocks__/responses/device-list-response.ts create mode 100644 ui/src/__mocks__/responses/user-response.ts create mode 100644 ui/src/api/openstf-api/index.ts create mode 100644 ui/src/api/openstf-api/openstf-api-client.ts create mode 100644 ui/src/api/openstf-api/routes.ts create mode 100644 ui/src/api/openstf/index.ts create mode 100644 ui/src/api/openstf/openstf-client.ts create mode 100644 ui/src/api/openstf/routes.ts create mode 100644 ui/src/api/openstf/types.ts create mode 100644 ui/src/api/socket.ts create mode 100644 ui/src/assets/device-hub.svg create mode 100644 ui/src/components/app/app-router.tsx create mode 100644 ui/src/components/app/app.tsx create mode 100644 ui/src/components/layouts/main-layout.tsx create mode 100644 ui/src/components/lib/conditional-render/conditional-render.test.tsx create mode 100644 ui/src/components/lib/conditional-render/conditional-render.tsx create mode 100644 ui/src/components/lib/conditional-render/index.ts create mode 100644 ui/src/components/lib/statistic-card/index.ts create mode 100644 ui/src/components/lib/statistic-card/statistic-card.module.css create mode 100644 ui/src/components/lib/statistic-card/statistic-card.tsx create mode 100644 ui/src/components/lib/statistic-card/types.ts create mode 100644 ui/src/components/ui/device-statistics/device-statistics.module.css create mode 100644 ui/src/components/ui/device-statistics/device-statistics.tsx create mode 100644 ui/src/components/ui/device-statistics/index.ts create mode 100644 ui/src/components/ui/device-table/cells/booked-before-cell.tsx create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/amazon-silk.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/android-browser.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/asus-browser.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/baidu.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/chrome-beta.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/chrome-canary.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/chrome-dev.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/chrome.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/explore.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/firefox-beta.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/firefox.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/fujitsu-fbrowser.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/google-android-browser.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/htc-sense-browser.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/lenovo-browser.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/maxthon.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/netstar-familysmile.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/one.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/opera-beta.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/opera-mini-native.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/opera-mini.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/opera.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/oppo-baidu-searchbox.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/puffin-free.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/samsung-popupbrowser.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/samsung-sbrowser.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/softbank-parentalcontrols.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/uc-mini.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/uc.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/yahoo-ybrowser.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/assets/yahoo-yjtop.png create mode 100644 ui/src/components/ui/device-table/cells/browser-cell/browser-cell.tsx create mode 100644 ui/src/components/ui/device-table/cells/device-status-cell.tsx create mode 100644 ui/src/components/ui/device-table/cells/index.ts create mode 100644 ui/src/components/ui/device-table/cells/link-cell.tsx create mode 100644 ui/src/components/ui/device-table/cells/model-cell/model-cell.module.css create mode 100644 ui/src/components/ui/device-table/cells/model-cell/model-cell.tsx create mode 100644 ui/src/components/ui/device-table/cells/notes-cell.tsx create mode 100644 ui/src/components/ui/device-table/cells/product-cell.tsx create mode 100644 ui/src/components/ui/device-table/cells/text-cell/text-cell.module.css create mode 100644 ui/src/components/ui/device-table/cells/text-cell/text-cell.tsx create mode 100644 ui/src/components/ui/device-table/columns.tsx create mode 100644 ui/src/components/ui/device-table/constants.ts create mode 100644 ui/src/components/ui/device-table/device-table.module.css create mode 100644 ui/src/components/ui/device-table/device-table.tsx create mode 100644 ui/src/components/ui/device-table/header-with-translation.tsx create mode 100644 ui/src/components/ui/device-table/helpers.tsx create mode 100644 ui/src/components/ui/device-table/index.ts create mode 100644 ui/src/components/ui/device-table/table-body.tsx create mode 100644 ui/src/components/ui/device-table/types.ts create mode 100644 ui/src/components/ui/header/header.module.css create mode 100644 ui/src/components/ui/header/header.tsx create mode 100644 ui/src/components/ui/header/index.ts create mode 100644 ui/src/components/ui/lang-switcher/index.ts create mode 100644 ui/src/components/ui/lang-switcher/lang-switcher.module.css create mode 100644 ui/src/components/ui/lang-switcher/lang-switcher.tsx create mode 100644 ui/src/components/ui/search-device/index.ts create mode 100644 ui/src/components/ui/search-device/search-device.module.css create mode 100644 ui/src/components/ui/search-device/search-device.tsx create mode 100644 ui/src/components/ui/table-column-visibility/index.ts create mode 100644 ui/src/components/ui/table-column-visibility/table-column-visibility.module.css create mode 100644 ui/src/components/ui/table-column-visibility/table-column-visibility.tsx create mode 100644 ui/src/components/views/control-page/control-page.async.tsx create mode 100644 ui/src/components/views/control-page/control-page.tsx create mode 100644 ui/src/components/views/control-page/index.ts create mode 100644 ui/src/components/views/devices-page/devices-page.async.tsx create mode 100644 ui/src/components/views/devices-page/devices-page.tsx create mode 100644 ui/src/components/views/devices-page/index.ts create mode 100644 ui/src/components/views/groups-page/groups-page.async.tsx create mode 100644 ui/src/components/views/groups-page/groups-page.tsx create mode 100644 ui/src/components/views/groups-page/index.ts create mode 100644 ui/src/components/views/settings-page/index.ts create mode 100644 ui/src/components/views/settings-page/settings-page.async.tsx create mode 100644 ui/src/components/views/settings-page/settings-page.module.css create mode 100644 ui/src/components/views/settings-page/settings-page.tsx create mode 100644 ui/src/components/views/settings-page/tabs/general-settings-tab.tsx create mode 100644 ui/src/config/i18n/i18n.ts create mode 100644 ui/src/config/queries/query-client.ts create mode 100644 ui/src/config/queries/query-key-store.ts create mode 100644 ui/src/constants/route-paths.ts create mode 100644 ui/src/generated/types/accessTokensResponse.ts create mode 100644 ui/src/generated/types/adbInstallFlagsPayload.ts create mode 100644 ui/src/generated/types/addAdbPublicKeyBody.ts create mode 100644 ui/src/generated/types/addOriginGroupDevicesParams.ts create mode 100644 ui/src/generated/types/addUserDevicePayload.ts create mode 100644 ui/src/generated/types/addUserDeviceV2Params.ts create mode 100644 ui/src/generated/types/addUserDeviceV3Params.ts create mode 100644 ui/src/generated/types/alertMessage.ts create mode 100644 ui/src/generated/types/alertMessagePayload.ts create mode 100644 ui/src/generated/types/alertMessagePayloadActivation.ts create mode 100644 ui/src/generated/types/alertMessagePayloadLevel.ts create mode 100644 ui/src/generated/types/alertMessageResponse.ts create mode 100644 ui/src/generated/types/autoTestResponse.ts create mode 100644 ui/src/generated/types/autoTestResponseGroup.ts create mode 100644 ui/src/generated/types/autoTestResponseGroupDevicesItem.ts create mode 100644 ui/src/generated/types/captureDevicesParams.ts create mode 100644 ui/src/generated/types/conflict.ts create mode 100644 ui/src/generated/types/conflictDate.ts create mode 100644 ui/src/generated/types/conflictOwner.ts create mode 100644 ui/src/generated/types/conflictsResponse.ts create mode 100644 ui/src/generated/types/createAccessTokenParams.ts create mode 100644 ui/src/generated/types/createServiceUserParams.ts create mode 100644 ui/src/generated/types/createUserAccessTokenParams.ts create mode 100644 ui/src/generated/types/createUserParams.ts create mode 100644 ui/src/generated/types/defaultResponse.ts create mode 100644 ui/src/generated/types/deleteDeviceParams.ts create mode 100644 ui/src/generated/types/deleteDevicesParams.ts create mode 100644 ui/src/generated/types/deleteUserParams.ts create mode 100644 ui/src/generated/types/deleteUsersParams.ts create mode 100644 ui/src/generated/types/device.ts create mode 100644 ui/src/generated/types/deviceBattery.ts create mode 100644 ui/src/generated/types/deviceBrowser.ts create mode 100644 ui/src/generated/types/deviceBrowserAppsItem.ts create mode 100644 ui/src/generated/types/deviceBrowserPhone.ts create mode 100644 ui/src/generated/types/deviceBrowserService.ts create mode 100644 ui/src/generated/types/deviceDisplay.ts create mode 100644 ui/src/generated/types/deviceGroup.ts create mode 100644 ui/src/generated/types/deviceGroupLifeTime.ts create mode 100644 ui/src/generated/types/deviceGroupOwner.ts create mode 100644 ui/src/generated/types/deviceListResponse.ts create mode 100644 ui/src/generated/types/deviceNetwork.ts create mode 100644 ui/src/generated/types/deviceOwner.ts create mode 100644 ui/src/generated/types/devicePayload.ts create mode 100644 ui/src/generated/types/devicePayloadStatus.ts create mode 100644 ui/src/generated/types/devicePhone.ts create mode 100644 ui/src/generated/types/deviceProvider.ts create mode 100644 ui/src/generated/types/deviceResponse.ts create mode 100644 ui/src/generated/types/deviceReverseForwardsItem.ts create mode 100644 ui/src/generated/types/deviceService.ts create mode 100644 ui/src/generated/types/devicesPayload.ts create mode 100644 ui/src/generated/types/errorResponse.ts create mode 100644 ui/src/generated/types/fingerPrintPayload.ts create mode 100644 ui/src/generated/types/freeDevicesParams.ts create mode 100644 ui/src/generated/types/getDeviceBookingsParams.ts create mode 100644 ui/src/generated/types/getDeviceBySerialParams.ts create mode 100644 ui/src/generated/types/getDeviceGroupsParams.ts create mode 100644 ui/src/generated/types/getDevicesParams.ts create mode 100644 ui/src/generated/types/getDevicesTarget.ts create mode 100644 ui/src/generated/types/getGroupDeviceParams.ts create mode 100644 ui/src/generated/types/getGroupDevicesParams.ts create mode 100644 ui/src/generated/types/getGroupParams.ts create mode 100644 ui/src/generated/types/getGroupUserParams.ts create mode 100644 ui/src/generated/types/getGroupUsersParams.ts create mode 100644 ui/src/generated/types/getGroupsParams.ts create mode 100644 ui/src/generated/types/getUserByEmailParams.ts create mode 100644 ui/src/generated/types/getUserDeviceBySerialParams.ts create mode 100644 ui/src/generated/types/getUserDeviceParams.ts create mode 100644 ui/src/generated/types/getUserDevicesParams.ts create mode 100644 ui/src/generated/types/getUserDevicesV2Params.ts create mode 100644 ui/src/generated/types/getUsersAlertMessageParams.ts create mode 100644 ui/src/generated/types/getUsersParams.ts create mode 100644 ui/src/generated/types/groupListResponse.ts create mode 100644 ui/src/generated/types/groupListResponseGroupsItem.ts create mode 100644 ui/src/generated/types/groupPayload.ts create mode 100644 ui/src/generated/types/groupPayloadClass.ts create mode 100644 ui/src/generated/types/groupPayloadState.ts create mode 100644 ui/src/generated/types/groupResponse.ts create mode 100644 ui/src/generated/types/groupResponseGroup.ts create mode 100644 ui/src/generated/types/groupsPayload.ts create mode 100644 ui/src/generated/types/index.ts create mode 100644 ui/src/generated/types/ownerResponse.ts create mode 100644 ui/src/generated/types/remoteConnectUserDeviceResponse.ts create mode 100644 ui/src/generated/types/removeOriginGroupDevicesParams.ts create mode 100644 ui/src/generated/types/response.ts create mode 100644 ui/src/generated/types/serviceUserResponse.ts create mode 100644 ui/src/generated/types/serviceUserResponseServiceUserInfo.ts create mode 100644 ui/src/generated/types/sizeResponse.ts create mode 100644 ui/src/generated/types/token.ts create mode 100644 ui/src/generated/types/typeResponse.ts create mode 100644 ui/src/generated/types/unexpectedErrorResponse.ts create mode 100644 ui/src/generated/types/updateDefaultUserGroupsQuotasParams.ts create mode 100644 ui/src/generated/types/updateStorageInfoParams.ts create mode 100644 ui/src/generated/types/updateUserGroupsQuotasParams.ts create mode 100644 ui/src/generated/types/useAndConnectDeviceBody.ts create mode 100644 ui/src/generated/types/userAccessTokenResponse.ts create mode 100644 ui/src/generated/types/userAccessTokensResponse.ts create mode 100644 ui/src/generated/types/userListResponse.ts create mode 100644 ui/src/generated/types/userListResponseUsersItem.ts create mode 100644 ui/src/generated/types/userResponse.ts create mode 100644 ui/src/generated/types/userResponseUser.ts create mode 100644 ui/src/generated/types/userResponseUserGroups.ts create mode 100644 ui/src/generated/types/userResponseUserGroupsQuotas.ts create mode 100644 ui/src/generated/types/userResponseUserGroupsQuotasAllocated.ts create mode 100644 ui/src/generated/types/userResponseUserGroupsQuotasConsumed.ts create mode 100644 ui/src/generated/types/userResponseUserSettings.ts create mode 100644 ui/src/generated/types/userResponseUserSettingsDeviceListColumnsItem.ts create mode 100644 ui/src/generated/types/userResponseUserSettingsDeviceListSort.ts create mode 100644 ui/src/generated/types/userResponseUserSettingsDeviceListSortFixedItem.ts create mode 100644 ui/src/generated/types/userResponseUserSettingsDeviceListSortUserItem.ts create mode 100644 ui/src/generated/types/userResponseUserSettingsGroupItemsPerPage.ts create mode 100644 ui/src/generated/types/usersPayload.ts create mode 100644 ui/src/generated/types/writeStatsBodyOne.ts create mode 100644 ui/src/generated/types/writeStatsBodyTwo.ts create mode 100644 ui/src/lib/hooks/use-click-outside.hook.ts create mode 100644 ui/src/lib/hooks/use-debounce.hook.ts create mode 100644 ui/src/lib/hooks/use-get-auth-contact.hook.ts create mode 100644 ui/src/lib/hooks/use-get-auth-docs.hook.ts create mode 100644 ui/src/lib/utils/capitalize-first-letter.util.ts create mode 100644 ui/src/lib/utils/date-to-formatted-string.util.ts create mode 100644 ui/src/lib/utils/device-service-to-string.util.test.ts create mode 100644 ui/src/lib/utils/device-service-to-string.util.ts create mode 100644 ui/src/lib/utils/generate-transaction-socket-channel.ts create mode 100644 ui/src/lib/utils/get-device-state.util.ts create mode 100644 ui/src/lib/utils/is-device-inactive.util.ts create mode 100644 ui/src/lib/utils/is-device-usable.util.test.ts create mode 100644 ui/src/lib/utils/is-device-usable.util.ts create mode 100644 ui/src/lib/utils/resolve-table-filter-value.util.ts create mode 100644 ui/src/lib/utils/resolve-table-fitler-value.util.test.ts create mode 100644 ui/src/main.tsx create mode 100644 ui/src/services/core/control-service.ts create mode 100644 ui/src/services/core/transaction-service/transaction-service.ts create mode 100644 ui/src/services/core/transaction-service/types.ts create mode 100644 ui/src/services/group-service.ts create mode 100644 ui/src/services/settings-service.ts create mode 100644 ui/src/store/current-user-profile-store.ts create mode 100644 ui/src/store/device-connection.ts create mode 100644 ui/src/store/device-list-store.ts create mode 100644 ui/src/store/device-table-state.ts create mode 100644 ui/src/store/mobx-query.ts create mode 100644 ui/src/styles/index.css create mode 100644 ui/src/styles/reset.css create mode 100644 ui/src/styles/variables/global.css create mode 100644 ui/src/types/array-type.type.ts create mode 100644 ui/src/types/column-group.type.ts create mode 100644 ui/src/types/device-change-message.type.ts create mode 100644 ui/src/types/enums/device-state.enum.ts create mode 100644 ui/src/vite-env.d.ts create mode 100644 ui/tsconfig.app.json create mode 100644 ui/tsconfig.json create mode 100644 ui/tsconfig.node.json create mode 100644 ui/vite.config.ts create mode 100644 ui/vitest-setup.ts diff --git a/.dockerignore b/.dockerignore index 1e0207b9a3..6bb9a4987d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,5 +10,7 @@ res/build/ rethinkdb_data/ temp/ tmp/ +doc/ .eslintcache scripts/ +.mypy_cache diff --git a/.editorconfig b/.editorconfig index 9912b2af9d..770798f83f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,7 +4,7 @@ root = true [*] indent_style = space -indent_size = 2 +indent_size = 4 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true diff --git a/.eslintignore b/.eslintignore index 02fc19f7f2..c7d0d42246 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,2 @@ /res/build/** /res/app/components/stf/screen/fast-image-render/pixi.js -WebDriverAgent diff --git a/.eslintrc b/.eslintrc index 754e18062d..85a3530405 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,10 +1,11 @@ { "extends": "eslint:recommended", "env": { - "node": true + "node": true, + "es6": true }, "parserOptions": { - "ecmaVersion": 2020, + "ecmaVersion": 2022, "sourceType": "module" }, "rules": { @@ -99,7 +100,7 @@ "func-style": 0, // optionally set `[2, "expression"]` "id-length": 0, // optionally set `[2, {"min": 3, "max": 10, "properties": "never", "exceptions": ["x"]}]` "id-match": 0, // optionally set `[2, "^[a-z]+([A-Z][a-z]+)*$", {"properties": false}]` - "indent": [0, 2, {"SwitchCase": 0, "VariableDeclarator": 2}], // TODO: optionally set `[2, 2, {"SwitchCase": 1, "VariableDeclarator": {"var": 2, "let": 2, "const": 3}}]` this gives too many errors + "indent": [2, 4, {"SwitchCase": 0, "VariableDeclarator": 2}], // TODO: optionally set `[2, 2, {"SwitchCase": 1, "VariableDeclarator": {"var": 2, "let": 2, "const": 3}}]` this gives too many errors "jsx-quotes": [2, "prefer-single"], "key-spacing": [2, {"beforeColon": false, "afterColon": true, "mode": "strict"}], // optionally set `[2, {"beforeColon": false, "afterColon": true, "mode": "strict", "align": "colon"}]` "lines-around-comment": 2, // optionally set `[2, {"beforeBlockComment": true, "beforeLineComment": true, "allowBlockStart": true}]` @@ -156,7 +157,7 @@ "no-sync": 1 // `2` is default // eslint v2 - //"keyword-spacing": 2 + // "keyword-spacing": 2 }, "globals": { "$window": true diff --git a/.gitignore b/.gitignore index 22da3e79e7..4820377f7e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ /rethinkdb_data/ /temp/ /tmp/ +.mypy_cache diff --git a/Dockerfile b/Dockerfile index a51712b354..5f8926b156 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,7 +54,12 @@ RUN set -x && \ mkdir /app/bundletool && \ mv /tmp/bundletool/* /app/bundletool && \ cd /app && \ - find /tmp -mindepth 1 ! -regex '^/tmp/hsperfdata_root\(/.*\)?' -delete + find /tmp -mindepth 1 ! -regex '^/tmp/hsperfdata_root\(/.*\)?' -delete && \ + ln -s /app/bin/stf.mjs /app/bin/stf &&\ + cd ui && \ + npm ci && \ + npx tsc -b && \ + npx vite build --mode staging # Switch to the app user. USER stf diff --git a/WebDriverAgent/.azure-pipelines.yml b/WebDriverAgent/.azure-pipelines.yml index a324a9b7df..89887d258a 100644 --- a/WebDriverAgent/.azure-pipelines.yml +++ b/WebDriverAgent/.azure-pipelines.yml @@ -1,19 +1,19 @@ # https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml variables: - MIN_VM_IMAGE: macOS-12 - MIN_XCODE_VERSION: 13.1 - MIN_PLATFORM_VERSION: 15.0 - MIN_TV_PLATFORM_VERSION: 15.0 - MIN_TV_DEVICE_NAME: Apple TV 4K (2nd generation) - MIN_IPHONE_DEVICE_NAME: iPhone 11 - MIN_IPAD_DEVICE_NAME: iPad Pro (11-inch) (3rd generation) - MAX_VM_IMAGE: macOS-12 - MAX_XCODE_VERSION: 14.2 - MAX_PLATFORM_VERSION: 16.2 - MAX_PLATFORM_VERSION_TV: 16.1 - MAX_IPHONE_DEVICE_NAME: iPhone 13 - MAX_TV_DEVICE_NAME: Apple TV 4K (2nd generation) - MAX_IPAD_DEVICE_NAME: iPad Pro (11-inch) (3rd generation) + MIN_VM_IMAGE: macOS-14 + MIN_XCODE_VERSION: 14.3.1 + MIN_PLATFORM_VERSION: 16.4 + MIN_TV_PLATFORM_VERSION: 16.4 + MIN_TV_DEVICE_NAME: Apple TV 4K (3rd generation) + MIN_IPHONE_DEVICE_NAME: iPhone 14 Plus + MIN_IPAD_DEVICE_NAME: iPad Pro (11-inch) (4th generation) + MAX_VM_IMAGE: macOS-14 + MAX_XCODE_VERSION: 15.4 + MAX_PLATFORM_VERSION: 17.5 + MAX_PLATFORM_VERSION_TV: 17.5 + MAX_IPHONE_DEVICE_NAME: iPhone 15 Plus + MAX_TV_DEVICE_NAME: Apple TV 4K (3rd generation) + MAX_IPAD_DEVICE_NAME: iPad Air 11-inch (M2) DEFAULT_NODE_VERSION: "18.x" trigger: diff --git a/WebDriverAgent/CHANGELOG.md b/WebDriverAgent/CHANGELOG.md index 2fb98c7d48..1a56783d3a 100644 --- a/WebDriverAgent/CHANGELOG.md +++ b/WebDriverAgent/CHANGELOG.md @@ -1,3 +1,39 @@ +## [8.11.1](https://github.com/appium/WebDriverAgent/compare/v8.11.0...v8.11.1) (2024-11-11) + +### Miscellaneous Chores + +* bump appium-ios-device ([#955](https://github.com/appium/WebDriverAgent/issues/955)) ([021f349](https://github.com/appium/WebDriverAgent/commit/021f34901866f4a7870914c00781b83bd0cbddc4)) + +## [8.11.0](https://github.com/appium/WebDriverAgent/compare/v8.10.1...v8.11.0) (2024-11-11) + +### Features + +* Add support for excluded_attributes in JSON source hierarchy ([#953](https://github.com/appium/WebDriverAgent/issues/953)) ([6112223](https://github.com/appium/WebDriverAgent/commit/6112223b21026fae5545fe1b1433a09c67ff524b)) + +## [8.10.1](https://github.com/appium/WebDriverAgent/compare/v8.10.0...v8.10.1) (2024-11-10) + +### Miscellaneous Chores + +* remove unnecessary lines ([#954](https://github.com/appium/WebDriverAgent/issues/954)) ([940df80](https://github.com/appium/WebDriverAgent/commit/940df80937381b481a2762fbf86b6249804591bd)) + +## [8.10.0](https://github.com/appium/WebDriverAgent/compare/v8.9.4...v8.10.0) (2024-11-07) + +### Features + +* add useClearTextShortcut setting ([#952](https://github.com/appium/WebDriverAgent/issues/952)) ([61bc051](https://github.com/appium/WebDriverAgent/commit/61bc051180d691d26233c66a5a76ed20b7fa09d2)) + +## [8.9.4](https://github.com/appium/WebDriverAgent/compare/v8.9.3...v8.9.4) (2024-10-17) + +### Bug Fixes + +* Consider transient overlay windows when respectSystemAlerts is enabled ([#946](https://github.com/appium/WebDriverAgent/issues/946)) ([f0bdce7](https://github.com/appium/WebDriverAgent/commit/f0bdce7eb8fdb13d2309d28e936950c77f006b20)) + +## [8.9.3](https://github.com/appium/WebDriverAgent/compare/v8.9.2...v8.9.3) (2024-10-07) + +### Miscellaneous Chores + +* remove unused FBBaseActionsParser and cleanup imports in FBConfiguration ([#943](https://github.com/appium/WebDriverAgent/issues/943)) ([a2173d0](https://github.com/appium/WebDriverAgent/commit/a2173d05df8ef831310e805a8e6a8a8d17725201)) + ## [8.9.2](https://github.com/appium/WebDriverAgent/compare/v8.9.1...v8.9.2) (2024-09-13) ### Miscellaneous Chores diff --git a/WebDriverAgent/Configurations/IOSSettings.xcconfig b/WebDriverAgent/Configurations/IOSSettings.xcconfig index 70ae5ec68a..b9969a48b2 100644 --- a/WebDriverAgent/Configurations/IOSSettings.xcconfig +++ b/WebDriverAgent/Configurations/IOSSettings.xcconfig @@ -28,4 +28,4 @@ RUN_CLANG_STATIC_ANALYZER = YES GCC_PREPROCESSOR_DEFINITIONS = $(inherited) -WARNING_CFLAGS = $(inherited) -Weverything -Wno-objc-missing-property-synthesis -Wno-unused-macros -Wno-disabled-macro-expansion -Wno-gnu-statement-expression -Wno-language-extension-token -Wno-overriding-method-mismatch -Wno-missing-variable-declarations -Rno-module-build -Wno-auto-import -Wno-objc-interface-ivars -Wno-documentation-unknown-command -Wno-reserved-id-macro -Wno-unused-parameter -Wno-gnu-conditional-omitted-operand -Wno-explicit-ownership-type -Wno-date-time -Wno-cast-align -Wno-cstring-format-directive -Wno-double-promotion -Wno-partial-availability +WARNING_CFLAGS = $(inherited) -Weverything -Wno-objc-missing-property-synthesis -Wno-unused-macros -Wno-disabled-macro-expansion -Wno-gnu-statement-expression -Wno-language-extension-token -Wno-overriding-method-mismatch -Wno-missing-variable-declarations -Rno-module-build -Wno-auto-import -Wno-objc-interface-ivars -Wno-documentation-unknown-command -Wno-reserved-id-macro -Wno-unused-parameter -Wno-gnu-conditional-omitted-operand -Wno-explicit-ownership-type -Wno-date-time -Wno-cast-align -Wno-cstring-format-directive -Wno-double-promotion -Wno-partial-availability -Wno-declaration-after-statement -Wno-objc-messaging-id -Wno-direct-ivar-access -Wno-cast-qual -Wno-deprecated-declarations diff --git a/WebDriverAgent/Configurations/TVOSSettings.xcconfig b/WebDriverAgent/Configurations/TVOSSettings.xcconfig index 70ae5ec68a..b9969a48b2 100644 --- a/WebDriverAgent/Configurations/TVOSSettings.xcconfig +++ b/WebDriverAgent/Configurations/TVOSSettings.xcconfig @@ -28,4 +28,4 @@ RUN_CLANG_STATIC_ANALYZER = YES GCC_PREPROCESSOR_DEFINITIONS = $(inherited) -WARNING_CFLAGS = $(inherited) -Weverything -Wno-objc-missing-property-synthesis -Wno-unused-macros -Wno-disabled-macro-expansion -Wno-gnu-statement-expression -Wno-language-extension-token -Wno-overriding-method-mismatch -Wno-missing-variable-declarations -Rno-module-build -Wno-auto-import -Wno-objc-interface-ivars -Wno-documentation-unknown-command -Wno-reserved-id-macro -Wno-unused-parameter -Wno-gnu-conditional-omitted-operand -Wno-explicit-ownership-type -Wno-date-time -Wno-cast-align -Wno-cstring-format-directive -Wno-double-promotion -Wno-partial-availability +WARNING_CFLAGS = $(inherited) -Weverything -Wno-objc-missing-property-synthesis -Wno-unused-macros -Wno-disabled-macro-expansion -Wno-gnu-statement-expression -Wno-language-extension-token -Wno-overriding-method-mismatch -Wno-missing-variable-declarations -Rno-module-build -Wno-auto-import -Wno-objc-interface-ivars -Wno-documentation-unknown-command -Wno-reserved-id-macro -Wno-unused-parameter -Wno-gnu-conditional-omitted-operand -Wno-explicit-ownership-type -Wno-date-time -Wno-cast-align -Wno-cstring-format-directive -Wno-double-promotion -Wno-partial-availability -Wno-declaration-after-statement -Wno-objc-messaging-id -Wno-direct-ivar-access -Wno-cast-qual -Wno-deprecated-declarations diff --git a/WebDriverAgent/WebDriverAgent.xcodeproj/project.pbxproj b/WebDriverAgent/WebDriverAgent.xcodeproj/project.pbxproj index 1feeb165c1..bfc4868641 100644 --- a/WebDriverAgent/WebDriverAgent.xcodeproj/project.pbxproj +++ b/WebDriverAgent/WebDriverAgent.xcodeproj/project.pbxproj @@ -3525,6 +3525,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 48LP2W34XH; ENABLE_TESTING_SEARCH_PATHS = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = WebDriverAgentRunner/Info.plist; @@ -3539,7 +3540,7 @@ ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.facebook.WebDriverAgentRunner; + PRODUCT_BUNDLE_IDENTIFIER = com.vkqa.WebDriverAgentRunner; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; TARGETED_DEVICE_FAMILY = 3; @@ -3585,6 +3586,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 48LP2W34XH; ENABLE_TESTING_SEARCH_PATHS = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = WebDriverAgentRunner/Info.plist; @@ -3599,7 +3601,7 @@ ); MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.facebook.WebDriverAgentRunner; + PRODUCT_BUNDLE_IDENTIFIER = com.vkqa.WebDriverAgentRunner; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; TARGETED_DEVICE_FAMILY = 3; @@ -3660,7 +3662,7 @@ /Developer/Library/Frameworks, ); OTHER_LDFLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = com.facebook.WebDriverAgentLib; + PRODUCT_BUNDLE_IDENTIFIER = com.vkqa.WebDriverAgentLib; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; SKIP_INSTALL = YES; @@ -3727,7 +3729,7 @@ ); ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = com.facebook.WebDriverAgentLib; + PRODUCT_BUNDLE_IDENTIFIER = com.vkqa.WebDriverAgentLib; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = appletvos; @@ -3958,9 +3960,11 @@ baseConfigurationReference = EEE5CABF1C80361500CBBDD9 /* IOSSettings.xcconfig */; buildSettings = { CLANG_STATIC_ANALYZER_MODE = deep; + CODE_SIGN_IDENTITY = "Apple Development"; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = NO; + DEVELOPMENT_TEAM = 48LP2W34XH; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -3982,7 +3986,7 @@ /System/Developer/Library/Frameworks, ); OTHER_LDFLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = com.facebook.WebDriverAgentLib; + PRODUCT_BUNDLE_IDENTIFIER = com.vkqa.WebDriverAgentLib; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -4023,8 +4027,10 @@ baseConfigurationReference = EEE5CABF1C80361500CBBDD9 /* IOSSettings.xcconfig */; buildSettings = { CLANG_STATIC_ANALYZER_MODE = deep; + CODE_SIGN_IDENTITY = "Apple Development"; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = NO; + DEVELOPMENT_TEAM = 48LP2W34XH; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -4047,7 +4053,7 @@ ); ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = com.facebook.WebDriverAgentLib; + PRODUCT_BUNDLE_IDENTIFIER = com.vkqa.WebDriverAgentLib; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -4200,7 +4206,6 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NONNULL = YES; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = 48LP2W34XH; INFOPLIST_FILE = WebDriverAgentTests/IntegrationApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -4217,7 +4222,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ANALYZER_NONNULL = YES; - DEVELOPMENT_TEAM = 48LP2W34XH; INFOPLIST_FILE = WebDriverAgentTests/IntegrationApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -4290,7 +4294,7 @@ "$(inherited)", "-all_load", ); - PRODUCT_BUNDLE_IDENTIFIER = com.di.WebDriverAgentRunner; + PRODUCT_BUNDLE_IDENTIFIER = com.vkqa.WebDriverAgentRunner; PRODUCT_NAME = "$(TARGET_NAME)"; USES_XCTRUNNER = YES; WARNING_CFLAGS = ( @@ -4344,7 +4348,7 @@ "$(inherited)", "-all_load", ); - PRODUCT_BUNDLE_IDENTIFIER = com.di.WebDriverAgentRunner; + PRODUCT_BUNDLE_IDENTIFIER = com.vkqa.WebDriverAgentRunner; PRODUCT_NAME = "$(TARGET_NAME)"; USES_XCTRUNNER = YES; WARNING_CFLAGS = ( diff --git a/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.h b/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.h index 3ef66423b3..54d4bd2816 100644 --- a/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.h +++ b/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.h @@ -31,6 +31,12 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSDictionary *)fb_tree; +/** + @param excludedAttributes Set of possible attributes to be excluded i.e frame, enabled, visible, accessible, focused. If set to nil or an empty array then no attributes will be excluded from the resulting JSON + @return application elements tree in form of nested dictionaries + */ +- (NSDictionary *)fb_tree:(nullable NSSet *) excludedAttributes; + /** Return application elements accessibility tree in form of nested dictionaries */ diff --git a/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m b/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m index c3c99e6f54..6cdae945bd 100644 --- a/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +++ b/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m @@ -152,11 +152,16 @@ - (BOOL)fb_deactivateWithDuration:(NSTimeInterval)duration error:(NSError **)err } - (NSDictionary *)fb_tree +{ + return [self fb_tree:nil]; +} + +- (NSDictionary *)fb_tree:(nullable NSSet *) excludedAttributes { id snapshot = self.fb_isResolvedFromCache.boolValue ? self.lastSnapshot : [self fb_snapshotWithAllAttributesAndMaxDepth:nil]; - return [self.class dictionaryForElement:snapshot recursive:YES]; + return [self.class dictionaryForElement:snapshot recursive:YES excludedAttributes:excludedAttributes]; } - (NSDictionary *)fb_accessibilityTree @@ -167,7 +172,9 @@ - (NSDictionary *)fb_accessibilityTree return [self.class accessibilityInfoForElement:snapshot]; } -+ (NSDictionary *)dictionaryForElement:(id)snapshot recursive:(BOOL)recursive ++ (NSDictionary *)dictionaryForElement:(id)snapshot + recursive:(BOOL)recursive + excludedAttributes:(nullable NSSet *) excludedAttributes { NSMutableDictionary *info = [[NSMutableDictionary alloc] init]; info[@"type"] = [FBElementTypeTransformer shortStringWithElementType:snapshot.elementType]; @@ -177,11 +184,35 @@ + (NSDictionary *)dictionaryForElement:(id)snapshot recursi info[@"value"] = FBValueOrNull(wrappedSnapshot.wdValue); info[@"label"] = FBValueOrNull(wrappedSnapshot.wdLabel); info[@"rect"] = wrappedSnapshot.wdRect; - info[@"frame"] = NSStringFromCGRect(wrappedSnapshot.wdFrame); - info[@"isEnabled"] = [@([wrappedSnapshot isWDEnabled]) stringValue]; - info[@"isVisible"] = [@([wrappedSnapshot isWDVisible]) stringValue]; - info[@"isAccessible"] = [@([wrappedSnapshot isWDAccessible]) stringValue]; - info[@"isFocused"] = [@([wrappedSnapshot isWDFocused]) stringValue]; + + NSDictionary *attributeBlocks = @{ + @"frame": ^{ + return NSStringFromCGRect(wrappedSnapshot.wdFrame); + }, + @"enabled": ^{ + return [@([wrappedSnapshot isWDEnabled]) stringValue]; + }, + @"visible": ^{ + return [@([wrappedSnapshot isWDVisible]) stringValue]; + }, + @"accessible": ^{ + return [@([wrappedSnapshot isWDAccessible]) stringValue]; + }, + @"focused": ^{ + return [@([wrappedSnapshot isWDFocused]) stringValue]; + } + }; + + for (NSString *key in attributeBlocks) { + if (excludedAttributes == nil || ![excludedAttributes containsObject:key]) { + NSString *value = ((NSString * (^)(void))attributeBlocks[key])(); + if ([key isEqualToString:@"frame"]) { + info[key] = value; + } else { + info[[NSString stringWithFormat:@"is%@", [key capitalizedString]]] = value; + } + } + } if (!recursive) { return info.copy; @@ -191,7 +222,9 @@ + (NSDictionary *)dictionaryForElement:(id)snapshot recursi if ([childElements count]) { info[@"children"] = [[NSMutableArray alloc] init]; for (id childSnapshot in childElements) { - [info[@"children"] addObject:[self dictionaryForElement:childSnapshot recursive:YES]]; + [info[@"children"] addObject:[self dictionaryForElement:childSnapshot + recursive:YES + excludedAttributes:excludedAttributes]]; } } return info; @@ -379,7 +412,9 @@ - (BOOL)fb_dismissKeyboardWithKeyNames:(nullable NSArray *)keyNames id extractedElement = extractIssueProperty(issue, @"element"); id elementSnapshot = [extractedElement fb_takeSnapshot]; - NSDictionary *elementAttributes = elementSnapshot ? [self.class dictionaryForElement:elementSnapshot recursive:NO] : @{}; + NSDictionary *elementAttributes = elementSnapshot + ? [self.class dictionaryForElement:elementSnapshot recursive:NO excludedAttributes:nil] + : @{}; [resultArray addObject:@{ @"detailedDescription": extractIssueProperty(issue, @"detailedDescription") ?: @"", diff --git a/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTyping.m b/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTyping.m index db516a220c..8b59ad0319 100644 --- a/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTyping.m +++ b/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTyping.m @@ -166,7 +166,7 @@ - (BOOL)fb_clearTextWithSnapshot:(FBXCElementSnapshotWrapper *)snapshot [self fb_prepareForTextInputWithSnapshot:snapshot]; } - if (retry == 0) { + if (retry == 0 && FBConfiguration.useClearTextShortcut) { // 1st attempt is via the IOHIDEvent as the fastest operation // https://github.com/appium/appium/issues/19389 [[XCUIDevice sharedDevice] fb_performIOHIDEventWithPage:0x07 // kHIDPage_KeyboardOrKeypad diff --git a/WebDriverAgent/WebDriverAgentLib/Commands/FBDebugCommands.m b/WebDriverAgent/WebDriverAgentLib/Commands/FBDebugCommands.m index dc96958d0f..c3f9a78160 100644 --- a/WebDriverAgent/WebDriverAgentLib/Commands/FBDebugCommands.m +++ b/WebDriverAgent/WebDriverAgentLib/Commands/FBDebugCommands.m @@ -54,7 +54,12 @@ + (NSArray *)routes withExcludedAttributes:excludedAttributes] withScope:sourceScope]]; } else if ([sourceType caseInsensitiveCompare:SOURCE_FORMAT_JSON] == NSOrderedSame) { - result = application.fb_tree; + NSString *excludedAttributesString = request.parameters[@"excluded_attributes"]; + NSSet *excludedAttributes = (excludedAttributesString == nil) + ? nil + : [NSSet setWithArray:[excludedAttributesString componentsSeparatedByString:@","]]; + + result = [application fb_tree:excludedAttributes]; } else if ([sourceType caseInsensitiveCompare:SOURCE_FORMAT_DESCRIPTION] == NSOrderedSame) { result = application.fb_descriptionRepresentation; } else { diff --git a/WebDriverAgent/WebDriverAgentLib/Commands/FBSessionCommands.m b/WebDriverAgent/WebDriverAgentLib/Commands/FBSessionCommands.m index c11c5eabb2..1972186d2c 100644 --- a/WebDriverAgent/WebDriverAgentLib/Commands/FBSessionCommands.m +++ b/WebDriverAgent/WebDriverAgentLib/Commands/FBSessionCommands.m @@ -351,6 +351,7 @@ + (NSArray *)routes FB_SETTING_DEFAULT_ALERT_ACTION: request.session.defaultAlertAction ?: @"", FB_SETTING_MAX_TYPING_FREQUENCY: @([FBConfiguration maxTypingFrequency]), FB_SETTING_RESPECT_SYSTEM_ALERTS: @([FBConfiguration shouldRespectSystemAlerts]), + FB_SETTING_USE_CLEAR_TEXT_SHORTCUT: @([FBConfiguration useClearTextShortcut]), #if !TARGET_OS_TV FB_SETTING_SCREENSHOT_ORIENTATION: [FBConfiguration humanReadableScreenshotOrientation], #endif @@ -449,6 +450,9 @@ + (NSArray *)routes if (nil != [settings objectForKey:FB_SETTING_MAX_TYPING_FREQUENCY]) { [FBConfiguration setMaxTypingFrequency:[[settings objectForKey:FB_SETTING_MAX_TYPING_FREQUENCY] unsignedIntegerValue]]; } + if (nil != [settings objectForKey:FB_SETTING_USE_CLEAR_TEXT_SHORTCUT]) { + [FBConfiguration setUseClearTextShortcut:[[settings objectForKey:FB_SETTING_USE_CLEAR_TEXT_SHORTCUT] boolValue]]; + } #if !TARGET_OS_TV if (nil != [settings objectForKey:FB_SETTING_SCREENSHOT_ORIENTATION]) { diff --git a/WebDriverAgent/WebDriverAgentLib/Info.plist b/WebDriverAgent/WebDriverAgentLib/Info.plist index a43dfce010..4dd4120c63 100644 --- a/WebDriverAgent/WebDriverAgentLib/Info.plist +++ b/WebDriverAgent/WebDriverAgentLib/Info.plist @@ -1,26 +1,26 @@ - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 8.9.2 - CFBundleSignature - ???? - CFBundleVersion - 8.9.2 - NSPrincipalClass - - - \ No newline at end of file + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 8.11.1 + CFBundleSignature + ???? + CFBundleVersion + 8.11.1 + NSPrincipalClass + + + diff --git a/WebDriverAgent/WebDriverAgentLib/Routing/FBSession.m b/WebDriverAgent/WebDriverAgentLib/Routing/FBSession.m index 14565ad007..a4460b4a88 100644 --- a/WebDriverAgent/WebDriverAgentLib/Routing/FBSession.m +++ b/WebDriverAgent/WebDriverAgentLib/Routing/FBSession.m @@ -178,8 +178,13 @@ - (XCUIApplication *)activeApplication if (nil != self.testedApplication) { XCUIApplicationState testedAppState = self.testedApplication.state; if (testedAppState >= XCUIApplicationStateRunningForeground) { + // We look for `SBTransientOverlayWindow` elements for half modals. See https://github.com/appium/WebDriverAgent/pull/946 + NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:@"%K == %@ OR %K == %@", + @"elementType", @(XCUIElementTypeAlert), + @"identifier", @"SBTransientOverlayWindow"]; if ([FBConfiguration shouldRespectSystemAlerts] - && [XCUIApplication.fb_systemApplication descendantsMatchingType:XCUIElementTypeAlert].count > 0) { + && [[XCUIApplication.fb_systemApplication descendantsMatchingType:XCUIElementTypeAny] + matchingPredicate:searchPredicate].count > 0) { return XCUIApplication.fb_systemApplication; } return (XCUIApplication *)self.testedApplication; diff --git a/WebDriverAgent/WebDriverAgentLib/Utilities/FBBaseActionsParser.m b/WebDriverAgent/WebDriverAgentLib/Utilities/FBBaseActionsParser.m deleted file mode 100644 index 872dc268e4..0000000000 --- a/WebDriverAgent/WebDriverAgentLib/Utilities/FBBaseActionsParser.m +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "FBBaseActionsSynthesizer.h" - -#import "FBErrorBuilder.h" - -@implementation FBBaseActionsSynthesizer - -- (instancetype)initWithActions:(NSArray *)actions forApplication:(XCUIApplication *)application -{ - self = [super init]; - if (self) { - _actions = actions; - _application = application; - } - return self; -} - -- (nullable XCSynthesizedEventRecord *)synthesizeWithError:(NSError **)error -{ - @throw [[FBErrorBuilder.builder withDescription:@"Override this method in subclasses"] build]; - return nil; -} - -@end diff --git a/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.h b/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.h index dc2018928e..7c337826cb 100644 --- a/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.h +++ b/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.h @@ -9,10 +9,6 @@ #import -#import "AXSettings.h" -#import "UIKeyboardImpl.h" -#import "TIPreferencesController.h" - NS_ASSUME_NONNULL_BEGIN extern NSString *const FBSnapshotMaxDepthKey; @@ -287,6 +283,15 @@ typedef NS_ENUM(NSInteger, FBConfigurationKeyboardPreference) { + (void)setDismissAlertButtonSelector:(NSString *)classChainSelector; + (NSString *)dismissAlertButtonSelector; +/** + * Whether to use HIDEvent for text clear. + * By default this is enabled and HIDEvent is used for text clear. + * + * @param enabled Either YES or NO + */ ++ (void)setUseClearTextShortcut:(BOOL)enabled; ++ (BOOL)useClearTextShortcut; + #if !TARGET_OS_TV /** Set the screenshot orientation for iOS diff --git a/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.m b/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.m index 1d7438ae18..dab8cb4dba 100644 --- a/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.m +++ b/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.m @@ -9,6 +9,10 @@ #import "FBConfiguration.h" +#import "AXSettings.h" +#import "UIKeyboardImpl.h" +#import "TIPreferencesController.h" + #include #import @@ -52,6 +56,7 @@ static NSTimeInterval FBAnimationCoolOffTimeout; static BOOL FBShouldUseCompactResponses; static NSString *FBElementResponseAttributes; +static BOOL FBUseClearTextShortcut; #if !TARGET_OS_TV static UIInterfaceOrientation FBScreenshotOrientation; #endif @@ -434,6 +439,16 @@ + (NSString *)dismissAlertButtonSelector return FBDismissAlertButtonSelector; } ++ (void)setUseClearTextShortcut:(BOOL)enabled +{ + FBUseClearTextShortcut = enabled; +} + ++ (BOOL)useClearTextShortcut +{ + return FBUseClearTextShortcut; +} + #if !TARGET_OS_TV + (BOOL)setScreenshotOrientation:(NSString *)orientation error:(NSError **)error { @@ -499,6 +514,7 @@ + (void)resetSessionSettings FBAnimationCoolOffTimeout = 2.; // 50 should be enough for the majority of the cases. The performance is acceptable for values up to 100. FBSetCustomParameterForElementSnapshot(FBSnapshotMaxDepthKey, @50); + FBUseClearTextShortcut = YES; #if !TARGET_OS_TV FBScreenshotOrientation = UIInterfaceOrientationUnknown; #endif diff --git a/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.h b/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.h index 5f44501104..b1f2f1fca5 100644 --- a/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.h +++ b/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.h @@ -40,6 +40,7 @@ extern NSString* const FB_SETTING_WAIT_FOR_IDLE_TIMEOUT; extern NSString* const FB_SETTING_ANIMATION_COOL_OFF_TIMEOUT; extern NSString* const FB_SETTING_MAX_TYPING_FREQUENCY; extern NSString* const FB_SETTING_RESPECT_SYSTEM_ALERTS; +extern NSString* const FB_SETTING_USE_CLEAR_TEXT_SHORTCUT; NS_ASSUME_NONNULL_END diff --git a/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.m b/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.m index a45afeb909..ada529eed5 100644 --- a/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.m +++ b/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.m @@ -35,3 +35,4 @@ NSString* const FB_SETTING_ANIMATION_COOL_OFF_TIMEOUT = @"animationCoolOffTimeout"; NSString* const FB_SETTING_MAX_TYPING_FREQUENCY = @"maxTypingFrequency"; NSString* const FB_SETTING_RESPECT_SYSTEM_ALERTS = @"respectSystemAlerts"; +NSString* const FB_SETTING_USE_CLEAR_TEXT_SHORTCUT = @"useClearTextShortcut"; diff --git a/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBVideoRecordingTests.m b/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBVideoRecordingTests.m index 617480c0c0..191af3420f 100644 --- a/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBVideoRecordingTests.m +++ b/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBVideoRecordingTests.m @@ -31,6 +31,8 @@ - (void)setUp - (void)testStartingAndStoppingVideoRecording { + XCTSkip(@"Failed on Azure Pipeline. Local run succeeded."); + // Video recording is only available since iOS 17 if (SYSTEM_VERSION_LESS_THAN(@"17.0")) { return; diff --git a/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIApplicationHelperTests.m b/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIApplicationHelperTests.m index e86f620217..794be2192f 100644 --- a/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIApplicationHelperTests.m +++ b/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIApplicationHelperTests.m @@ -44,6 +44,13 @@ - (void)testApplicationTree XCTAssertNotNil(self.testedApplication.fb_accessibilityTree); } +- (void)testApplicationTreeAttributesFiltering +{ + NSDictionary *applicationTree = [self.testedApplication fb_tree:[NSSet setWithArray:@[@"visible"]]]; + XCTAssertNotNil(applicationTree); + XCTAssertNil([applicationTree objectForKey:@"isVisible"], @"'isVisible' key should not be present in the application tree"); +} + - (void)testDeactivateApplication { NSError *error; @@ -111,7 +118,25 @@ - (void)testAccessbilityAudit [set addObject:@"XCUIAccessibilityAuditTypeAll"]; NSArray *auditIssues2 = [XCUIApplication.fb_activeApplication fb_performAccessibilityAuditWithAuditTypesSet:set.copy error:&error]; - XCTAssertEqualObjects(auditIssues1, auditIssues2); + // 'elementDescription' is not in this list because it could have + // different object id's debug description in XCTest. + NSArray *checkKeys = @[ + @"auditType", + @"compactDescription", + @"detailedDescription", + @"element", + @"elementAttributes" + ]; + + XCTAssertEqual([auditIssues1 count], [auditIssues2 count]); + for (int i = 1; i < [auditIssues1 count]; i++) { + for (NSString *k in checkKeys) { + XCTAssertEqualObjects( + [auditIssues1[i] objectForKey:k], + [auditIssues2[i] objectForKey:k] + ); + } + } XCTAssertNil(error); } diff --git a/WebDriverAgent/lib/types.ts b/WebDriverAgent/lib/types.ts index 8b41dfc4ef..82c51cb15b 100644 --- a/WebDriverAgent/lib/types.ts +++ b/WebDriverAgent/lib/types.ts @@ -25,6 +25,7 @@ export interface WDASettings { waitForIdleTimeout?: number; animationCoolOffTimeout?: number; maxTypingFrequency?: number; + useClearTextShortcut?: boolean; } // WebDriverAgentLib/Utilities/FBCapabilities.h diff --git a/WebDriverAgent/package.json b/WebDriverAgent/package.json index ced94dc6b0..a06baeeebf 100644 --- a/WebDriverAgent/package.json +++ b/WebDriverAgent/package.json @@ -1,6 +1,6 @@ { "name": "appium-webdriveragent", - "version": "8.9.2", + "version": "8.11.1", "description": "Package bundling WebDriverAgent", "main": "./build/index.js", "types": "./build/index.d.ts", @@ -76,7 +76,7 @@ "@appium/base-driver": "^9.0.0", "@appium/strongbox": "^0.x", "@appium/support": "^5.0.3", - "appium-ios-device": "^2.5.0", + "appium-ios-device": "^2.7.25", "appium-ios-simulator": "^6.0.0", "async-lock": "^1.0.0", "asyncbox": "^3.0.0", diff --git a/bin/package.json b/bin/package.json new file mode 100644 index 0000000000..9172ed0504 --- /dev/null +++ b/bin/package.json @@ -0,0 +1,3 @@ +{ + "type":"module" +} diff --git a/bin/stf b/bin/stf deleted file mode 100755 index aab975b056..0000000000 --- a/bin/stf +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env -S node --experimental-detect-module -import '../lib/cli/index.js' diff --git a/bin/stf.mjs b/bin/stf.mjs new file mode 100755 index 0000000000..2c3ce86b33 --- /dev/null +++ b/bin/stf.mjs @@ -0,0 +1,3 @@ +#!/usr/bin/env -S node --import ./lib/util/instrument.mjs +console.log('Starting stf') +import '../lib/cli/index.js' diff --git a/build.sh b/build.sh index 176a2c0242..5e635592cb 100755 --- a/build.sh +++ b/build.sh @@ -1,5 +1,3 @@ -# export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" -# [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" nvm use 20 npm ci -npm unlink stf && npm link && ALLOW_OUTDATED_DEPENDENCIES=1 stf local --allow-remote +npm unlink stf && npm link && stf local --allow-remote diff --git a/doc/units.md b/doc/units.md index ddb3559283..c4b2a8162b 100644 --- a/doc/units.md +++ b/doc/units.md @@ -1,5 +1,70 @@ # stf +## Connections chart: +```mermaid +flowchart LR; +subgraph triproxy-app + triproxy-app-pub(pub) + %% 7150 + triproxy-app-dealer(dealer) + %% 7160 + triproxy-app-pull(pull) + %% 7170 +end +triproxy-app-dealer --> triproxy-app-pub +triproxy-app-pull --> triproxy-app-dealer +subgraph triproxy-dev; + triproxy-dev-pub(pub) + %% 7250 + triproxy-dev-dealer(dealer) + %% 7260 + triproxy-dev-pull(pull) + %% 7270 +end +triproxy-dev-dealer --> triproxy-dev-pub +triproxy-dev-pull --> triproxy-dev-dealer + +subgraph api + api-sub(sub) + api-push(push) +end +api-sub -- ZMQ 7150 --> triproxy-app-pub; +api-push -- ZMQ 7170 --> triproxy-app-pull; +api-sub -- ZMQ 7250 --> triproxy-dev-pub; +api-push -- ZMQ 7270 --> triproxy-dev-pull; + +subgraph processor; + processor-app-dealer(app-dealer) + processor-dev-dealer(dev-dealer) +end +processor-app-dealer -- ZMQ 7160 --> triproxy-app-dealer; +processor-dev-dealer -- ZMQ 7260 --> triproxy-dev-dealer; + +subgraph reaper; + reaper-push(push) + reaper-sub(sub) +end; +reaper-push -- ZMQ 7270 --> triproxy-dev-pull +reaper-sub -- ZMQ 7150 --> triproxy-app-pub + +subgraph storage; + storage-push(push) + storage-sub(sub) +end + +storage-push -- ZMQ 7270 --> triproxy-dev-pull +storage-sub -- ZMQ 7150 --> triproxy-app-pub + + +subgraph websocket; + websocket-sub(sub) + websocket-push(push) +end + +websocket-sub -- ZMQ 7150 --> triproxy-app-pub +websocket-push -- ZMQ 7170 --> triproxy-app-pull +``` + ## Install ```sh diff --git a/docker-compose-dev.yaml b/docker-compose-dev.yaml index 90e907b6bd..d5f75a8ae0 100644 --- a/docker-compose-dev.yaml +++ b/docker-compose-dev.yaml @@ -8,7 +8,7 @@ services: entrypoint: ["mongod", "--replSet", "myReplicaSet", "--bind_ip_all"] networks: default: - ipv4_address: 172.19.0.2 + ipv4_address: 10.211.0.2 mongo2: image: mongo:6.0.10 hostname: mongo2 @@ -16,7 +16,7 @@ services: entrypoint: ["mongod", "--replSet", "myReplicaSet", "--bind_ip_all"] networks: default: - ipv4_address: 172.19.0.3 + ipv4_address: 10.211.0.3 mongo3: image: mongo:6.0.10 hostname: mongo3 @@ -24,7 +24,7 @@ services: entrypoint: ["mongod", "--replSet", "myReplicaSet", "--bind_ip_all"] networks: default: - ipv4_address: 172.19.0.4 + ipv4_address: 10.211.0.4 mongosetup: image: mongo:6.0.10 depends_on: @@ -44,12 +44,12 @@ services: privileged: true networks: default: - ipv4_address: 172.19.0.6 + ipv4_address: 10.211.0.6 networks: default: driver: bridge ipam: driver: default config: - - subnet: 172.19.0.0/16 - gateway: 172.19.0.1 + - subnet: 10.211.0.0/16 + gateway: 10.211.0.1 diff --git a/docker-compose-prod.yaml b/docker-compose-prod.yaml index 93fe69a276..63518d96bf 100644 --- a/docker-compose-prod.yaml +++ b/docker-compose-prod.yaml @@ -1,13 +1,12 @@ -version: "3" networks: - default: + devicehub: driver: bridge ipam: driver: default config: - - subnet: 172.19.0.0/16 - gateway: 172.19.0.1 + - subnet: 10.211.0.0/16 + gateway: 10.211.0.1 services: nginx: @@ -26,6 +25,8 @@ services: - devicehub-api - devicehub-auth restart: unless-stopped + networks: + devicehub: mongo1: image: mongo:6.0.10 hostname: mongo1 @@ -34,24 +35,24 @@ services: - 27017:27017 entrypoint: ["mongod", "--replSet", "myReplicaSet", "--bind_ip_all"] networks: - default: - ipv4_address: 172.19.0.2 + devicehub: + ipv4_address: 10.211.0.2 mongo2: image: mongo:6.0.10 hostname: mongo2 container_name: mongo2 entrypoint: ["mongod", "--replSet", "myReplicaSet", "--bind_ip_all"] networks: - default: - ipv4_address: 172.19.0.3 + devicehub: + ipv4_address: 10.211.0.3 mongo3: image: mongo:6.0.10 hostname: mongo3 container_name: mongo3 entrypoint: ["mongod", "--replSet", "myReplicaSet", "--bind_ip_all"] networks: - default: - ipv4_address: 172.19.0.4 + devicehub: + ipv4_address: 10.211.0.4 mongosetup: image: mongo:6.0.10 container_name: mongosetup @@ -63,6 +64,8 @@ services: - ./scripts/mongo_setup.sh:/scripts/mongo_setup.sh restart: "no" entrypoint: ["bash", "/scripts/mongo_setup.sh"] + networks: + devicehub: devicehub-migrate: build: . image: devicehub @@ -73,6 +76,8 @@ services: depends_on: mongosetup: condition: service_completed_successfully + networks: + devicehub: devicehub-app: build: . image: devicehub @@ -84,6 +89,8 @@ services: devicehub-migrate: condition: service_completed_successfully restart: unless-stopped + networks: + devicehub: devicehub-auth: build: . image: devicehub @@ -95,6 +102,8 @@ services: devicehub-migrate: condition: service_completed_successfully restart: unless-stopped + networks: + devicehub: devicehub-processor001: build: . image: devicehub @@ -106,6 +115,8 @@ services: devicehub-migrate: condition: service_completed_successfully restart: unless-stopped + networks: + devicehub: devicehub-reaper: build: . image: devicehub @@ -117,6 +128,8 @@ services: devicehub-migrate: condition: service_completed_successfully restart: unless-stopped + networks: + devicehub: devicehub-storage-plugin-apk: build: . image: devicehub @@ -128,6 +141,8 @@ services: devicehub-migrate: condition: service_completed_successfully restart: unless-stopped + networks: + devicehub: devicehub-storage-plugin-image: build: . image: devicehub @@ -139,6 +154,8 @@ services: devicehub-migrate: condition: service_completed_successfully restart: unless-stopped + networks: + devicehub: devicehub-storage-temp: build: . image: devicehub @@ -150,6 +167,8 @@ services: devicehub-migrate: condition: service_completed_successfully restart: unless-stopped + networks: + devicehub: devicehub-triproxy-app: build: . image: devicehub @@ -165,6 +184,8 @@ services: devicehub-migrate: condition: service_completed_successfully restart: unless-stopped + networks: + devicehub: devicehub-triproxy-dev: build: . image: devicehub @@ -180,6 +201,8 @@ services: devicehub-migrate: condition: service_completed_successfully restart: unless-stopped + networks: + devicehub: devicehub-websocket: build: . image: devicehub @@ -191,6 +214,8 @@ services: devicehub-migrate: condition: service_completed_successfully restart: unless-stopped + networks: + devicehub: devicehub-api: build: . image: devicehub @@ -202,6 +227,8 @@ services: devicehub-migrate: condition: service_completed_successfully restart: unless-stopped + networks: + devicehub: devicehub-api-groups-engine: build: . image: devicehub @@ -213,6 +240,8 @@ services: devicehub-migrate: condition: service_completed_successfully restart: unless-stopped + networks: + devicehub: volumes: devicehub-db-volume: diff --git a/docker-compose-test.yaml b/docker-compose-test.yaml index 6da5887d36..43272dab93 100644 --- a/docker-compose-test.yaml +++ b/docker-compose-test.yaml @@ -9,7 +9,7 @@ services: ["mongod", "--replSet", "myReplicaSet", "--bind_ip", "localhost,mongo1"] networks: default: - ipv4_address: 172.19.0.2 + ipv4_address: 10.211.0.2 mongo2: image: mongo:6.0.10 hostname: mongo2 @@ -18,7 +18,7 @@ services: ["mongod", "--replSet", "myReplicaSet", "--bind_ip", "localhost,mongo2"] networks: default: - ipv4_address: 172.19.0.3 + ipv4_address: 10.211.0.3 mongo3: image: mongo:6.0.10 hostname: mongo3 @@ -27,7 +27,7 @@ services: ["mongod", "--replSet", "myReplicaSet", "--bind_ip", "localhost,mongo3"] networks: default: - ipv4_address: 172.19.0.4 + ipv4_address: 10.211.0.4 mongosetup: image: mongo:6.0.10 depends_on: @@ -47,7 +47,7 @@ services: privileged: true networks: default: - ipv4_address: 172.19.0.6 + ipv4_address: 10.211.0.6 devicehub: container_name: devicehub build: . @@ -58,19 +58,19 @@ services: - "7400-7500:7400-7500" environment: - TZ='America/Los_Angeles' - - MONGODB_PORT_27017_TCP=mongodb://172.19.0.2:27017 + - MONGODB_PORT_27017_TCP=mongodb://10.211.0.2:27017 - STF_ADMIN_EMAIL='test@example.com' - STF_ADMIN_NAME='test admin' restart: unless-stopped command: stf local --adb-host adb --public-ip 0.0.0.0 --provider-min-port 7400 --provider-max-port 7500 networks: default: - ipv4_address: 172.19.0.5 + ipv4_address: 10.211.0.5 networks: default: driver: bridge ipam: driver: default config: - - subnet: 172.19.0.0/16 - gateway: 172.19.0.1 + - subnet: 10.211.0.0/16 + gateway: 10.211.0.1 diff --git a/gulpfile.js b/gulpfile.js index c60979a5b8..d9f2d03edd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2,8 +2,8 @@ import path from 'path' import gulp from 'gulp' import gutil from 'gulp-util' import jsonlint from 'gulp-jsonlint' -import eslint from 'gulp-eslint' -import eslintIfFixed from 'gulp-eslint-if-fixed' +import gulpIf from 'gulp-if' +import gulpESLintNew from 'gulp-eslint-new' import {ESLint} from 'eslint' import webpack from 'webpack' import webpackConfig from './webpack.config.cjs' @@ -61,17 +61,19 @@ gulp.task('lint-fix', function() { 'lib/**/*.js' , 'res/**/*.js' ]) - .pipe(eslint({fix: true})) - .pipe(eslintIfFixed(file => { - isContainsFixes = true - return file.base - })) - .pipe(eslint.format()) - .pipe(eslint.failAfterError()).on('end', function() { - if (isContainsFixes) { - throw new Error('Eslint repaired code in files (try to re-commit)') - } - }) + .pipe(gulpESLintNew({fix: true})) + .pipe(gulpIf((file) => ( + file.eslint !== undefined && file.eslint.fixed + ), gulp.dest((file => { + isContainsFixes = true + return file.base + })))) + .pipe(gulpESLintNew.format()) + .pipe(gulpESLintNew.failAfterError()).on('end', function() { + if (isContainsFixes) { + throw new Error('Eslint repaired code in files (try to re-commit)') + } + }) }) gulp.task('run:checkversion', function() { gutil.log('Checking STF version...') @@ -103,14 +105,14 @@ gulp.task('protractor-explorer', function(callback) { gulp.task('protractor', gulp.series('webdriver-update', function(callback) { gulp.src(['./res/test/e2e/**/*.js']) .pipe(protractor.protractor({ - configFile: protractorConfig - , debug: gutil.env.debug - , suite: gutil.env.suite - })) + configFile: protractorConfig + , debug: gutil.env.debug + , suite: gutil.env.suite + })) .on('error', function(e) { - console.log(e) + console.error(e) /* eslint no-console: 0 */ - }) + }) .on('end', callback) })) // For piping strings @@ -173,14 +175,14 @@ gulp.task('pug', function() { './res/**/*.pug' ]) .pipe(pug({ - locals: { + locals: { // So res/views/docs.pug doesn't complain - markdownFile: { - parseContent: function() { + markdownFile: { + parseContent: function() { + } } } - } - })) + })) .pipe(gulp.dest('./tmp/html/')) }) gulp.task('translate:extract', gulp.series('pug', function() { @@ -195,8 +197,8 @@ gulp.task('translate:extract', gulp.series('pug', function() { gulp.task('translate:compile', function() { return gulp.src('./res/common/lang/po/**/*.po') .pipe(gettext.compile({ - format: 'json' - })) + format: 'json' + })) .pipe(gulp.dest('./res/common/lang/translations/')) }) gulp.task('translate:push', function() { diff --git a/lib/cli/api/index.js b/lib/cli/api/index.js index 4eac2edef7..04d72ea918 100644 --- a/lib/cli/api/index.js +++ b/lib/cli/api/index.js @@ -6,55 +6,55 @@ export const builder = function(yargs) { .env('STF_API') .strict() .option('connect-push', { - alias: 'c' - , describe: 'App-side ZeroMQ PULL endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'c' + , describe: 'App-side ZeroMQ PULL endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-sub', { - alias: 'u' - , describe: 'App-side ZeroMQ PUB endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'u' + , describe: 'App-side ZeroMQ PUB endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-push-dev', { - alias: 'pd' - , describe: 'Device-side ZeroMQ PULL endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'pd' + , describe: 'Device-side ZeroMQ PULL endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-sub-dev', { - alias: 'sd' - , describe: 'Device-side ZeroMQ PUB endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'sd' + , describe: 'Device-side ZeroMQ PUB endpoint to connect to.' + , array: true + , demand: true + }) .option('port', { - alias: 'p' - , describe: 'The port to bind to.' - , type: 'number' - , default: process.env.PORT || 7106 - }) + alias: 'p' + , describe: 'The port to bind to.' + , type: 'number' + , default: process.env.PORT || 7106 + }) .option('secret', { - alias: 's' - , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + + alias: 's' + , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + 'knows this token can freely enter the system if they want, so keep ' + 'it safe.' - , type: 'string' - , default: process.env.SECRET - , demand: true - }) + , type: 'string' + , default: process.env.SECRET + , demand: true + }) .option('ssid', { - alias: 'i' - , describe: 'The name of the session ID cookie.' - , type: 'string' - , default: process.env.SSID || 'ssid' - }) + alias: 'i' + , describe: 'The name of the session ID cookie.' + , type: 'string' + , default: process.env.SSID || 'ssid' + }) .option('storage-url', { - alias: 'r' - , describe: 'The URL to the storage unit.' - , type: 'string' - }) + alias: 'r' + , describe: 'The URL to the storage unit.' + , type: 'string' + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_API_` (e.g. ' + diff --git a/lib/cli/app/index.js b/lib/cli/app/index.js index 4ee358ebb3..7de51f7724 100644 --- a/lib/cli/app/index.js +++ b/lib/cli/app/index.js @@ -6,42 +6,42 @@ export const builder = function(yargs) { .env('STF_APP') .strict() .option('auth-url', { - alias: 'a' - , describe: 'URL to the auth unit.' - , type: 'string' - , demand: true - }) + alias: 'a' + , describe: 'URL to the auth unit.' + , type: 'string' + , demand: true + }) .option('port', { - alias: 'p' - , describe: 'The port to bind to.' - , type: 'number' - , default: process.env.PORT || 7105 - }) + alias: 'p' + , describe: 'The port to bind to.' + , type: 'number' + , default: process.env.PORT || 7105 + }) .option('secret', { - alias: 's' - , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + + alias: 's' + , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + 'knows this token can freely enter the system if they want, so keep ' + 'it safe.' - , type: 'string' - , default: process.env.SECRET - , demand: true - }) + , type: 'string' + , default: process.env.SECRET + , demand: true + }) .option('ssid', { - alias: 'i' - , describe: 'The name of the session ID cookie.' - , type: 'string' - , default: process.env.SSID || 'ssid' - }) + alias: 'i' + , describe: 'The name of the session ID cookie.' + , type: 'string' + , default: process.env.SSID || 'ssid' + }) .option('user-profile-url', { - describe: 'URL to an external user profile page.' - , type: 'string' - }) + describe: 'URL to an external user profile page.' + , type: 'string' + }) .option('websocket-url', { - alias: 'w' - , describe: 'URL to the websocket unit.' - , type: 'string' - , demand: true - }) + alias: 'w' + , describe: 'URL to the websocket unit.' + , type: 'string' + , demand: true + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_APP_` (e.g. ' + diff --git a/lib/cli/auth-ldap/index.js b/lib/cli/auth-ldap/index.js index 06775821ae..94faf57db1 100644 --- a/lib/cli/auth-ldap/index.js +++ b/lib/cli/auth-ldap/index.js @@ -6,98 +6,98 @@ export const builder = function(yargs) { .env('STF_AUTH_LDAP') .strict() .option('app-url', { - alias: 'a' - , describe: 'URL to the app unit.' - , type: 'string' - , demand: true - }) + alias: 'a' + , describe: 'URL to the app unit.' + , type: 'string' + , demand: true + }) .option('ldap-bind-credentials', { - describe: 'LDAP bind credentials.' - , type: 'string' - , default: process.env.LDAP_BIND_CREDENTIALS - }) + describe: 'LDAP bind credentials.' + , type: 'string' + , default: process.env.LDAP_BIND_CREDENTIALS + }) .option('ldap-bind-dn', { - describe: 'LDAP bind DN.' - , type: 'string' - , default: process.env.LDAP_BIND_DN - }) + describe: 'LDAP bind DN.' + , type: 'string' + , default: process.env.LDAP_BIND_DN + }) .option('ldap-search-class', { - describe: 'LDAP search objectClass.' - , type: 'string' - , default: process.env.LDAP_SEARCH_CLASS || 'top' - }) + describe: 'LDAP search objectClass.' + , type: 'string' + , default: process.env.LDAP_SEARCH_CLASS || 'top' + }) .option('ldap-search-dn', { - describe: 'LDAP search DN.' - , type: 'string' - , default: process.env.LDAP_SEARCH_DN - , demand: true - }) + describe: 'LDAP search DN.' + , type: 'string' + , default: process.env.LDAP_SEARCH_DN + , demand: true + }) .option('ldap-search-field', { - describe: 'LDAP search field.' - , type: 'string' - , default: process.env.LDAP_SEARCH_FIELD - , demand: true - }) + describe: 'LDAP search field.' + , type: 'string' + , default: process.env.LDAP_SEARCH_FIELD + , demand: true + }) .option('ldap-search-scope', { - describe: 'LDAP search scope.' - , type: 'string' - , default: process.env.LDAP_SEARCH_SCOPE || 'sub' - }) + describe: 'LDAP search scope.' + , type: 'string' + , default: process.env.LDAP_SEARCH_SCOPE || 'sub' + }) .option('ldap-search-filter', { - describe: 'LDAP search filter.' - , type: 'string' - , default: process.env.LDAP_SEARCH_FILTER - }) + describe: 'LDAP search filter.' + , type: 'string' + , default: process.env.LDAP_SEARCH_FILTER + }) .option('ldap-timeout', { - alias: 't' - , describe: 'LDAP timeout.' - , type: 'number' - , default: process.env.LDAP_TIMEOUT || 1000 - }) + alias: 't' + , describe: 'LDAP timeout.' + , type: 'number' + , default: process.env.LDAP_TIMEOUT || 1000 + }) .option('ldap-url', { - alias: 'u' - , describe: 'URL to the LDAP server (e.g. `ldap://127.0.0.1`).' - , type: 'string' - , default: process.env.LDAP_URL - , demand: true - }) + alias: 'u' + , describe: 'URL to the LDAP server (e.g. `ldap://127.0.0.1`).' + , type: 'string' + , default: process.env.LDAP_URL + , demand: true + }) .option('ldap-username-field', { - describe: 'LDAP username field.' - , type: 'string' - , default: process.env.LDAP_USERNAME_FIELD || 'cn' - , demand: true - }) + describe: 'LDAP username field.' + , type: 'string' + , default: process.env.LDAP_USERNAME_FIELD || 'cn' + , demand: true + }) .option('port', { - alias: 'p' - , describe: 'The port to bind to.' - , type: 'number' - , default: process.env.PORT || 7120 - }) + alias: 'p' + , describe: 'The port to bind to.' + , type: 'number' + , default: process.env.PORT || 7120 + }) .option('secret', { - alias: 's' - , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + + alias: 's' + , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + 'knows this token can freely enter the system if they want, so keep ' + 'it safe.' - , type: 'string' - , default: process.env.SECRET - , demand: true - }) + , type: 'string' + , default: process.env.SECRET + , demand: true + }) .option('ssid', { - alias: 'i' - , describe: 'The name of the session ID cookie.' - , type: 'string' - , default: process.env.SSID || 'ssid' - }) + alias: 'i' + , describe: 'The name of the session ID cookie.' + , type: 'string' + , default: process.env.SSID || 'ssid' + }) .option('support', { - alias: 'sl' - , describe: 'url which needed to access support' - , type: 'string' - }) + alias: 'sl' + , describe: 'url which needed to access support' + , type: 'string' + }) .option('docsUrl', { - alias: 'du' - , describe: 'url which needed to access docs' - , type: 'string' - }) + alias: 'du' + , describe: 'url which needed to access docs' + , type: 'string' + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_AUTH_LDAP_` (e.g. ' + diff --git a/lib/cli/auth-mock/index.js b/lib/cli/auth-mock/index.js index e6efc76341..aaa75133f5 100644 --- a/lib/cli/auth-mock/index.js +++ b/lib/cli/auth-mock/index.js @@ -6,58 +6,58 @@ export const builder = function(yargs) { .env('STF_AUTH_MOCK') .strict() .option('app-url', { - alias: 'a' - , describe: 'URL to the app unit.' - , type: 'string' - , demand: true - }) + alias: 'a' + , describe: 'URL to the app unit.' + , type: 'string' + , demand: true + }) .option('basic-auth-password', { - describe: 'Basic auth password (if enabled).' - , type: 'string' - , default: process.env.BASIC_AUTH_PASSWORD - }) + describe: 'Basic auth password (if enabled).' + , type: 'string' + , default: process.env.BASIC_AUTH_PASSWORD + }) .option('basic-auth-username', { - describe: 'Basic auth username (if enabled).' - , type: 'string' - , default: process.env.BASIC_AUTH_USERNAME - }) + describe: 'Basic auth username (if enabled).' + , type: 'string' + , default: process.env.BASIC_AUTH_USERNAME + }) .option('port', { - alias: 'p' - , describe: 'The port to bind to.' - , type: 'number' - , default: process.env.PORT || 7120 - }) + alias: 'p' + , describe: 'The port to bind to.' + , type: 'number' + , default: process.env.PORT || 7120 + }) .option('secret', { - alias: 's' - , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + + alias: 's' + , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + 'knows this token can freely enter the system if they want, so keep ' + 'it safe.' - , type: 'string' - , default: process.env.SECRET - , demand: true - }) + , type: 'string' + , default: process.env.SECRET + , demand: true + }) .option('ssid', { - alias: 'i' - , describe: 'The name of the session ID cookie.' - , type: 'string' - , default: process.env.SSID || 'ssid' - }) + alias: 'i' + , describe: 'The name of the session ID cookie.' + , type: 'string' + , default: process.env.SSID || 'ssid' + }) .option('use-basic-auth', { - describe: 'Whether to "secure" the login page with basic authentication.' - , type: 'boolean' - }) + describe: 'Whether to "secure" the login page with basic authentication.' + , type: 'boolean' + }) .option('support', { - alias: 'sl' - , describe: 'url which needed to access support' - , type: 'string' - , default: 'example.com' - }) + alias: 'sl' + , describe: 'url which needed to access support' + , type: 'string' + , default: 'example.com' + }) .option('docsUrl', { - alias: 'du' - , describe: 'url which needed to access docs' - , type: 'string' - , default: 'example.com' - }) + alias: 'du' + , describe: 'url which needed to access docs' + , type: 'string' + , default: 'example.com' + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_AUTH_MOCK_` (e.g. ' + diff --git a/lib/cli/auth-oauth2/index.js b/lib/cli/auth-oauth2/index.js index 21494341e8..9e56a06612 100644 --- a/lib/cli/auth-oauth2/index.js +++ b/lib/cli/auth-oauth2/index.js @@ -6,97 +6,97 @@ export const builder = function(yargs) { .env('STF_AUTH_OAUTH2') .strict() .option('app-url', { - alias: 'a' - , describe: 'URL to the app unit.' - , type: 'string' - , demand: true - }) + alias: 'a' + , describe: 'URL to the app unit.' + , type: 'string' + , demand: true + }) .option('oauth-authorization-url', { - describe: 'OAuth 2.0 authorization URL.' - , type: 'string' - , default: process.env.OAUTH_AUTHORIZATION_URL - , demand: true - }) + describe: 'OAuth 2.0 authorization URL.' + , type: 'string' + , default: process.env.OAUTH_AUTHORIZATION_URL + , demand: true + }) .option('oauth-token-url', { - describe: 'OAuth 2.0 token URL.' - , type: 'string' - , default: process.env.OAUTH_TOKEN_URL - , demand: true - }) + describe: 'OAuth 2.0 token URL.' + , type: 'string' + , default: process.env.OAUTH_TOKEN_URL + , demand: true + }) .option('oauth-userinfo-url', { - describe: 'OAuth 2.0 user info URL.' - , type: 'string' - , default: process.env.OAUTH_USERINFO_URL - , demand: true - }) + describe: 'OAuth 2.0 user info URL.' + , type: 'string' + , default: process.env.OAUTH_USERINFO_URL + , demand: true + }) .option('oauth-client-id', { - describe: 'OAuth 2.0 client ID.' - , type: 'string' - , default: process.env.OAUTH_CLIENT_ID - , demand: true - }) + describe: 'OAuth 2.0 client ID.' + , type: 'string' + , default: process.env.OAUTH_CLIENT_ID + , demand: true + }) .option('oauth-client-secret', { - describe: 'OAuth 2.0 client secret.' - , type: 'string' - , default: process.env.OAUTH_CLIENT_SECRET - , demand: true - }) + describe: 'OAuth 2.0 client secret.' + , type: 'string' + , default: process.env.OAUTH_CLIENT_SECRET + , demand: true + }) .option('oauth-callback-url', { - describe: 'OAuth 2.0 callback URL.' - , type: 'string' - , default: process.env.OAUTH_CALLBACK_URL - , demand: true - }) + describe: 'OAuth 2.0 callback URL.' + , type: 'string' + , default: process.env.OAUTH_CALLBACK_URL + , demand: true + }) .option('oauth-scope', { - describe: 'Space-separated OAuth 2.0 scope.' - , type: 'string' - , default: process.env.OAUTH_SCOPE - , demand: true - }) + describe: 'Space-separated OAuth 2.0 scope.' + , type: 'string' + , default: process.env.OAUTH_SCOPE + , demand: true + }) .option('oauth-state', { - describe: 'Whether to enable OAuth 2.0 state token support.' - , type: 'boolean' - , default: false - }) + describe: 'Whether to enable OAuth 2.0 state token support.' + , type: 'boolean' + , default: false + }) .option('oauth-domain', { - describe: 'Optional email domain to allow authentication for.' - , type: 'string' - , default: process.env.OAUTH_DOMAIN - , demand: false - }) + describe: 'Optional email domain to allow authentication for.' + , type: 'string' + , default: process.env.OAUTH_DOMAIN + , demand: false + }) .option('port', { - alias: 'p' - , describe: 'The port to bind to.' - , type: 'number' - , default: process.env.PORT || 7120 - }) + alias: 'p' + , describe: 'The port to bind to.' + , type: 'number' + , default: process.env.PORT || 7120 + }) .option('secret', { - alias: 's' - , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + + alias: 's' + , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + 'knows this token can freely enter the system if they want, so keep ' + 'it safe.' - , type: 'string' - , default: process.env.SECRET - , demand: true - }) + , type: 'string' + , default: process.env.SECRET + , demand: true + }) .option('ssid', { - alias: 'i' - , describe: 'The name of the session ID cookie.' - , type: 'string' - , default: process.env.SSID || 'ssid' - }) + alias: 'i' + , describe: 'The name of the session ID cookie.' + , type: 'string' + , default: process.env.SSID || 'ssid' + }) .option('support', { - alias: 'sl' - , describe: 'url which needed to access support' - , type: 'string' - , default: 'example.com' - }) + alias: 'sl' + , describe: 'url which needed to access support' + , type: 'string' + , default: 'example.com' + }) .option('docsUrl', { - alias: 'du' - , describe: 'url which needed to access docs' - , type: 'string' - , default: 'example.com' - }) + alias: 'du' + , describe: 'url which needed to access docs' + , type: 'string' + , default: 'example.com' + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_AUTH_OAUTH2_` (e.g. ' + diff --git a/lib/cli/auth-openid/index.js b/lib/cli/auth-openid/index.js index d98d96b485..47d1d5b9e1 100644 --- a/lib/cli/auth-openid/index.js +++ b/lib/cli/auth-openid/index.js @@ -6,60 +6,60 @@ export const builder = function(yargs) { .env('STF_AUTH_OPENID') .strict() .option('app-url', { - alias: 'a' - , describe: 'URL to the app unit.' - , type: 'string' - , demand: true - }) + alias: 'a' + , describe: 'URL to the app unit.' + , type: 'string' + , demand: true + }) .option('openid-identifier-url', { - describe: 'OpenID identifier URL.' - , type: 'string' - , default: process.env.OPENID_IDENTIFIER_URL - , demand: true - }) + describe: 'OpenID identifier URL.' + , type: 'string' + , default: process.env.OPENID_IDENTIFIER_URL + , demand: true + }) .option('port', { - alias: 'p' - , describe: 'The port to bind to.' - , type: 'number' - , default: process.env.PORT || 7120 - }) + alias: 'p' + , describe: 'The port to bind to.' + , type: 'number' + , default: process.env.PORT || 7120 + }) .option('secret', { - alias: 's' - , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + + alias: 's' + , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + 'knows this token can freely enter the system if they want, so keep ' + 'it safe.' - , type: 'string' - , default: process.env.SECRET - , demand: true - }) + , type: 'string' + , default: process.env.SECRET + , demand: true + }) .option('ssid', { - alias: 'i' - , describe: 'The name of the session ID cookie.' - , type: 'string' - , default: process.env.SSID || 'ssid' - }) + alias: 'i' + , describe: 'The name of the session ID cookie.' + , type: 'string' + , default: process.env.SSID || 'ssid' + }) .option('openid-client-id', { - describe: 'openid-connect clientId' - , type: 'string' - , demand: true - }) + describe: 'openid-connect clientId' + , type: 'string' + , demand: true + }) .option('openid-client-secret', { - describe: 'openid-connect client secret' - , type: 'string' - , demand: true - }) + describe: 'openid-connect client secret' + , type: 'string' + , demand: true + }) .option('support', { - alias: 'sl' - , describe: 'url which needed to access support' - , type: 'string' - , default: 'example.com' - }) + alias: 'sl' + , describe: 'url which needed to access support' + , type: 'string' + , default: 'example.com' + }) .option('docsUrl', { - alias: 'du' - , describe: 'url which needed to access docs' - , type: 'string' - , default: 'example.com' - }) + alias: 'du' + , describe: 'url which needed to access docs' + , type: 'string' + , default: 'example.com' + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_AUTH_OPENID_` (e.g. ' + diff --git a/lib/cli/auth-saml2/index.js b/lib/cli/auth-saml2/index.js index 41574700e0..cb291f2c2f 100644 --- a/lib/cli/auth-saml2/index.js +++ b/lib/cli/auth-saml2/index.js @@ -6,67 +6,67 @@ export const builder = function(yargs) { .env('STF_AUTH_SAML2') .strict() .option('app-url', { - alias: 'a' - , describe: 'URL to the app unit.' - , type: 'string' - , demand: true - }) + alias: 'a' + , describe: 'URL to the app unit.' + , type: 'string' + , demand: true + }) .option('port', { - alias: 'p' - , describe: 'The port to bind to.' - , type: 'number' - , default: process.env.PORT || 7120 - }) + alias: 'p' + , describe: 'The port to bind to.' + , type: 'number' + , default: process.env.PORT || 7120 + }) .option('saml-id-provider-entry-point-url', { - describe: 'SAML 2.0 identity provider URL.' - , type: 'string' - , default: process.env.SAML_ID_PROVIDER_ENTRY_POINT_URL - , demand: true - }) + describe: 'SAML 2.0 identity provider URL.' + , type: 'string' + , default: process.env.SAML_ID_PROVIDER_ENTRY_POINT_URL + , demand: true + }) .option('saml-id-provider-issuer', { - describe: 'SAML 2.0 identity provider issuer.' - , type: 'string' - , default: process.env.SAML_ID_PROVIDER_ISSUER - , demand: true - }) + describe: 'SAML 2.0 identity provider issuer.' + , type: 'string' + , default: process.env.SAML_ID_PROVIDER_ISSUER + , demand: true + }) .option('saml-id-provider-cert-path', { - describe: 'SAML 2.0 identity provider certificate file path.' - , type: 'string' - , default: process.env.SAML_ID_PROVIDER_CERT_PATH - }) + describe: 'SAML 2.0 identity provider certificate file path.' + , type: 'string' + , default: process.env.SAML_ID_PROVIDER_CERT_PATH + }) .option('saml-id-provider-callback-url', { - describe: 'SAML 2.0 identity provider callback URL ' + + describe: 'SAML 2.0 identity provider callback URL ' + 'in the form of scheme://host[:port]/auth/saml/callback.' - , type: 'string' - , default: process.env.SAML_ID_PROVIDER_CALLBACK_URL - }) + , type: 'string' + , default: process.env.SAML_ID_PROVIDER_CALLBACK_URL + }) .option('secret', { - alias: 's' - , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + + alias: 's' + , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + 'knows this token can freely enter the system if they want, so keep ' + 'it safe.' - , type: 'string' - , default: process.env.SECRET - , demand: true - }) + , type: 'string' + , default: process.env.SECRET + , demand: true + }) .option('ssid', { - alias: 'i' - , describe: 'The name of the session ID cookie.' - , type: 'string' - , default: process.env.SSID || 'ssid' - }) + alias: 'i' + , describe: 'The name of the session ID cookie.' + , type: 'string' + , default: process.env.SSID || 'ssid' + }) .option('support', { - alias: 'sl' - , describe: 'url which needed to access support' - , type: 'string' - , default: 'example.com' - }) + alias: 'sl' + , describe: 'url which needed to access support' + , type: 'string' + , default: 'example.com' + }) .option('docsUrl', { - alias: 'du' - , describe: 'url which needed to access docs' - , type: 'string' - , default: 'example.com' - }) + alias: 'du' + , describe: 'url which needed to access docs' + , type: 'string' + , default: 'example.com' + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_AUTH_SAML2_` (e.g. ' + diff --git a/lib/cli/device/index.js b/lib/cli/device/index.js index 2a7f55df01..af1aca1e5a 100644 --- a/lib/cli/device/index.js +++ b/lib/cli/device/index.js @@ -4,186 +4,186 @@ export const builder = function(yargs) { return yargs .strict() .option('adb-host', { - describe: 'The ADB server host.' - , type: 'string' - , default: '127.0.0.1' - }) + describe: 'The ADB server host.' + , type: 'string' + , default: '127.0.0.1' + }) .option('adb-port', { - describe: 'The ADB server port.' - , type: 'number' - , default: 5037 - }) + describe: 'The ADB server port.' + , type: 'number' + , default: 5037 + }) .option('boot-complete-timeout', { - describe: 'How long to wait for boot to complete during device setup.' - , type: 'number' - , default: 60000 - }) + describe: 'How long to wait for boot to complete during device setup.' + , type: 'number' + , default: 60000 + }) .option('cleanup', { - describe: 'Attempt to reset the device between uses by uninstalling' + + describe: 'Attempt to reset the device between uses by uninstalling' + 'apps, resetting accounts and clearing caches. Does not do a perfect ' + 'job currently. Negate with --no-cleanup.' - , type: 'boolean' - , default: true - }) + , type: 'boolean' + , default: true + }) .option('cleanup-disable-bluetooth', { - describe: 'Whether to disable Bluetooth during cleanup.' - , type: 'boolean' - , default: false - }) + describe: 'Whether to disable Bluetooth during cleanup.' + , type: 'boolean' + , default: false + }) .option('cleanup-bluetooth-bonds', { - describe: 'Whether to remove Bluetooth bonds during cleanup.' - , type: 'boolean' - , default: false - }) + describe: 'Whether to remove Bluetooth bonds during cleanup.' + , type: 'boolean' + , default: false + }) .option('connect-port', { - describe: 'Port allocated to adb connections.' - , type: 'number' - , demand: true - }) + describe: 'Port allocated to adb connections.' + , type: 'number' + , demand: true + }) .option('connect-push', { - alias: 'p' - , describe: 'ZeroMQ PULL endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'p' + , describe: 'ZeroMQ PULL endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-sub', { - alias: 's' - , describe: 'ZeroMQ PUB endpoint to connect to.' - , array: true - , demand: true - }) + alias: 's' + , describe: 'ZeroMQ PUB endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-url-pattern', { - describe: 'The URL pattern to use for `adb connect`.' - , type: 'string' - , default: '${publicIp}:${publicPort}' - }) + describe: 'The URL pattern to use for `adb connect`.' + , type: 'string' + , default: '${publicIp}:${publicPort}' + }) .option('group-timeout', { - alias: 't' - , describe: 'Timeout in seconds for automatic release of inactive devices.' - , type: 'number' - , default: 900 - }) + alias: 't' + , describe: 'Timeout in seconds for automatic release of inactive devices.' + , type: 'number' + , default: 900 + }) .option('heartbeat-interval', { - describe: 'Send interval in milliseconds for heartbeat messages.' - , type: 'number' - , default: 10000 - }) + describe: 'Send interval in milliseconds for heartbeat messages.' + , type: 'number' + , default: 10000 + }) .option('lock-rotation', { - describe: 'Whether to lock rotation when devices are being used. ' + + describe: 'Whether to lock rotation when devices are being used. ' + 'Otherwise changing device orientation may not always work due to ' + 'sensitive sensors quickly or immediately reverting it back to the ' + 'physical orientation.' - , type: 'boolean' - }) + , type: 'boolean' + }) .option('mute-master', { - describe: 'Whether to mute master volume.' - , choices: ['always', 'inuse', 'never'] - , default: 'never' - , coerce: val => { - if (val === true) { - return 'inuse' // For backwards compatibility. - } - if (val === false) { - return 'never' // For backwards compatibility. + describe: 'Whether to mute master volume.' + , choices: ['always', 'inuse', 'never'] + , default: 'never' + , coerce: val => { + if (val === true) { + return 'inuse' // For backwards compatibility. + } + if (val === false) { + return 'never' // For backwards compatibility. + } + return val } - return val - } - }) + }) .option('provider', { - alias: 'n' - , describe: 'Name of the provider.' - , type: 'string' - , demand: true - }) + alias: 'n' + , describe: 'Name of the provider.' + , type: 'string' + , demand: true + }) .option('public-ip', { - describe: 'The IP or hostname to use in URLs.' - , type: 'string' - , demand: true - }) + describe: 'The IP or hostname to use in URLs.' + , type: 'string' + , demand: true + }) .option('screen-frame-rate', { - describe: 'The frame rate (frames/s) to be used for screen transport on the network. ' + + describe: 'The frame rate (frames/s) to be used for screen transport on the network. ' + 'Float value must be > 0.0 otherwise the default behavior is kept' - , type: 'number' - , default: process.env.SCREEN_FRAME_RATE || -1 - }) + , type: 'number' + , default: process.env.SCREEN_FRAME_RATE || -1 + }) .option('screen-jpeg-quality', { - describe: 'The JPG quality to use for the screen.' - , type: 'number' - , default: process.env.SCREEN_JPEG_QUALITY || 1 // 80 - }) + describe: 'The JPG quality to use for the screen.' + , type: 'number' + , default: process.env.SCREEN_JPEG_QUALITY || 1 // 80 + }) .option('screen-grabber', { - describe: 'The tool to be used for screen capture. ' + + describe: 'The tool to be used for screen capture. ' + 'Value must be either: minicap-bin (default) or minicap-apk' - , type: 'string' - , default: process.env.SCREEN_GRABBER || 'minicap-bin' - }) + , type: 'string' + , default: process.env.SCREEN_GRABBER || 'minicap-bin' + }) .option('screen-ping-interval', { - describe: 'The interval at which to send ping messages to keep the ' + + describe: 'The interval at which to send ping messages to keep the ' + 'screen WebSocket alive.' - , type: 'number' - , default: 30000 - }) + , type: 'number' + , default: 30000 + }) .option('screen-port', { - describe: 'Port allocated to the screen WebSocket.' - , type: 'number' - , demand: true - }) + describe: 'Port allocated to the screen WebSocket.' + , type: 'number' + , demand: true + }) .option('screen-reset', { - describe: 'Go back to home screen and reset screen rotation ' + + describe: 'Go back to home screen and reset screen rotation ' + 'when user releases device. Negate with --no-screen-reset.' - , type: 'boolean' - , default: true - }) + , type: 'boolean' + , default: true + }) .option('screen-ws-url-pattern', { - describe: 'The URL pattern to use for the screen WebSocket.' - , type: 'string' - , default: 'ws://${publicIp}:${publicPort}' // using ws because we can't use wss on localhost - }) + describe: 'The URL pattern to use for the screen WebSocket.' + , type: 'string' + , default: 'ws://${publicIp}:${publicPort}' // using ws because we can't use wss on localhost + }) .option('serial', { - describe: 'The USB serial number of the device.' - , type: 'string' - , demand: true - }) + describe: 'The USB serial number of the device.' + , type: 'string' + , demand: true + }) .option('storage-url', { - alias: 'r' - , describe: 'The URL to the storage unit.' - , type: 'string' - , demand: true - }) + alias: 'r' + , describe: 'The URL to the storage unit.' + , type: 'string' + , demand: true + }) .option('vnc-initial-size', { - describe: 'The initial size to use for the experimental VNC server.' - , type: 'string' - , default: '600x800' - , coerce: function(val) { - return val.split('x').map(Number) - } - }) + describe: 'The initial size to use for the experimental VNC server.' + , type: 'string' + , default: '600x800' + , coerce: function(val) { + return val.split('x').map(Number) + } + }) .option('vnc-port', { - describe: 'Port allocated to VNC connections.' - , type: 'number' - , demand: true - }) + describe: 'Port allocated to VNC connections.' + , type: 'number' + , demand: true + }) .option('need-scrcpy', { - describe: 'Need using Scrcpy instead Minicap for screen capture.' - , type: 'boolean' - , default: false - }) + describe: 'Need using Scrcpy instead Minicap for screen capture.' + , type: 'boolean' + , default: false + }) .option('device-name', { - describe: 'Device name' - , type: 'string' - , default: false - }) + describe: 'Device name' + , type: 'string' + , default: false + }) .option('host', { - describe: 'Provider hostname.' - , type: 'string' - , demand: true - , default: '127.0.0.1' - }) - .option('url-without-adb-port', { - describe: 'If there isnt adbPort in DB use baseUrl.' - , type: 'boolean' - , default: true - }) + describe: 'Provider hostname.' + , type: 'string' + , demand: true + , default: '127.0.0.1' + }) + .option('url-without-adb-port', { + describe: 'If there isnt adbPort in DB use baseUrl.' + , type: 'boolean' + , default: true + }) } export const handler = function(argv) { return device({ @@ -220,5 +220,5 @@ export const handler = function(argv) { , deviceName: argv.deviceName , host: argv.host , urlWithoutAdbPort: argv.urlWithoutAdbPort - }) + }) } diff --git a/lib/cli/doctor/index.js b/lib/cli/doctor/index.js index db9898b081..983d2744f3 100644 --- a/lib/cli/doctor/index.js +++ b/lib/cli/doctor/index.js @@ -75,21 +75,21 @@ export const handler = function() { return semver.satisfies(sanitizedVersion, wantedVersion) }) .then(function(satisfied) { - if (!satisfied) { - throw new CheckError('%s is currently %s but needs to be %s', label, currentVersion, wantedVersion) - } - }) + if (!satisfied) { + throw new CheckError('%s is currently %s but needs to be %s', label, currentVersion, wantedVersion) + } + }) } } return Promise.try(function() { return fn(new Check()) }) .catch(CheckError, function(err) { - log.error(err.message) - }) + log.error(err.message) + }) .catch(function(err) { - log.error('Unexpected error checking %s: %s', label, err) - }) + log.error('Unexpected error checking %s: %s', label, err) + }) } function checkOSArch() { log.info('OS Arch: %s', os.arch()) diff --git a/lib/cli/generate-fake-device/index.js b/lib/cli/generate-fake-device/index.js index d6d044002f..f2bae758b2 100644 --- a/lib/cli/generate-fake-device/index.js +++ b/lib/cli/generate-fake-device/index.js @@ -5,11 +5,11 @@ export const builder = function(yargs) { return yargs .strict() .option('n', { - alias: 'number' - , describe: 'How many devices to create.' - , type: 'number' - , default: 1 - }) + alias: 'number' + , describe: 'How many devices to create.' + , type: 'number' + , default: 1 + }) } export const handler = function(argv) { var log = logger.createLogger('cli:generate-fake-device') @@ -22,10 +22,10 @@ export const handler = function(argv) { } return next() .then(function() { - process.exit(0) - }) + process.exit(0) + }) .catch(function(err) { - log.fatal('Fake device creation had an error:', err.stack) - process.exit(1) - }) + log.fatal('Fake device creation had an error:', err.stack) + process.exit(1) + }) } diff --git a/lib/cli/generate-fake-group/index.js b/lib/cli/generate-fake-group/index.js index 77fd1e993c..33fd8d5f34 100644 --- a/lib/cli/generate-fake-group/index.js +++ b/lib/cli/generate-fake-group/index.js @@ -5,11 +5,11 @@ export const builder = function(yargs) { return yargs .strict() .option('n', { - alias: 'number' - , describe: 'How many groups to create.' - , type: 'number' - , default: 1 - }) + alias: 'number' + , describe: 'How many groups to create.' + , type: 'number' + , default: 1 + }) } export const handler = function(argv) { var log = logger.createLogger('cli:generate-fake-group') @@ -22,10 +22,10 @@ export const handler = function(argv) { } return next() .then(function() { - process.exit(0) - }) + process.exit(0) + }) .catch(function(err) { - log.fatal('Fake group creation had an error:', err.stack) - process.exit(1) - }) + log.fatal('Fake group creation had an error:', err.stack) + process.exit(1) + }) } diff --git a/lib/cli/generate-fake-user/index.js b/lib/cli/generate-fake-user/index.js index 2357697003..62a679de14 100644 --- a/lib/cli/generate-fake-user/index.js +++ b/lib/cli/generate-fake-user/index.js @@ -5,11 +5,11 @@ export const builder = function(yargs) { return yargs .strict() .option('n', { - alias: 'number' - , describe: 'How many users to create.' - , type: 'number' - , default: 1 - }) + alias: 'number' + , describe: 'How many users to create.' + , type: 'number' + , default: 1 + }) } export const handler = function(argv) { var log = logger.createLogger('cli:generate-fake-user') @@ -22,10 +22,10 @@ export const handler = function(argv) { } return next() .then(function() { - process.exit(0) - }) + process.exit(0) + }) .catch(function(err) { - log.fatal('Fake user creation had an error:', err.stack) - process.exit(1) - }) + log.fatal('Fake user creation had an error:', err.stack) + process.exit(1) + }) } diff --git a/lib/cli/generate-service-user/index.js b/lib/cli/generate-service-user/index.js index a4ea0ad5aa..41016341c7 100644 --- a/lib/cli/generate-service-user/index.js +++ b/lib/cli/generate-service-user/index.js @@ -5,27 +5,27 @@ export const builder = function(yargs) { return yargs .strict() .option('admin', { - alias: 'admin' - , describe: 'user need to be admin' - , type: 'boolean' - , default: false - }) + alias: 'admin' + , describe: 'user need to be admin' + , type: 'boolean' + , default: false + }) .option('name', { - alias: 'name' - , describe: 'display name for user' - , type: 'string' - }) + alias: 'name' + , describe: 'display name for user' + , type: 'string' + }) .option('email', { - alias: 'email' - , describe: 'email address' - , type: 'string' - }) + alias: 'email' + , describe: 'email address' + , type: 'string' + }) .option('secret', { - alias: 'secret' - , describe: 'secret used in tokens' - , type: 'string' - , default: 'kute kittykat' // this secret used in stf local run only lib/cli/local/index.js:58 - }) + alias: 'secret' + , describe: 'secret used in tokens' + , type: 'string' + , default: 'kute kittykat' // this secret used in stf local run only lib/cli/local/index.js:58 + }) } export const handler = function(argv) { const log = logger.createLogger('cli:generate-service-user') @@ -40,10 +40,10 @@ export const handler = function(argv) { } return next() .then(function() { - process.exit(0) - }) + process.exit(0) + }) .catch(function(err) { - log.fatal('Service user creation had an error:', err.stack) - process.exit(1) - }) + log.fatal('Service user creation had an error:', err.stack) + process.exit(1) + }) } diff --git a/lib/cli/groups-engine/index.js b/lib/cli/groups-engine/index.js index 58b026cb00..f1e4735b73 100644 --- a/lib/cli/groups-engine/index.js +++ b/lib/cli/groups-engine/index.js @@ -6,29 +6,29 @@ export const builder = function(yargs) { .env('STF_GROUPS_ENGINE') .strict() .option('connect-push', { - alias: 'c' - , describe: 'App-side ZeroMQ PULL endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'c' + , describe: 'App-side ZeroMQ PULL endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-sub', { - alias: 'u' - , describe: 'App-side ZeroMQ PUB endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'u' + , describe: 'App-side ZeroMQ PUB endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-push-dev', { - alias: 'pd' - , describe: 'Device-side ZeroMQ PULL endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'pd' + , describe: 'Device-side ZeroMQ PULL endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-sub-dev', { - alias: 'sd' - , describe: 'Device-side ZeroMQ PUB endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'sd' + , describe: 'Device-side ZeroMQ PUB endpoint to connect to.' + , array: true + , demand: true + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_GROUPS_ENGINE_` .)') diff --git a/lib/cli/index.js b/lib/cli/index.js index f771d99309..f19c695682 100644 --- a/lib/cli/index.js +++ b/lib/cli/index.js @@ -39,40 +39,40 @@ import * as triproxy from './triproxy/index.js' import * as websocket from './websocket/index.js' yargs(hideBin(process.argv)).usage('Usage: $0 [options]') - .strict() - .command(api) - .command(app) - .command(authLdap) - .command(authMock) - .command(authOauth2) - .command(authOpenid) - .command(authSaml2) - .command(groupsEngine) - .command(device) - .command(iosDevice) - .command(vncDevice) - .command(doctor) - .command(generateFakeDevice) - .command(generateFakeUser) - .command(generateFakeGroup) - .command(generateServiceUser) - .command(local) - .command(logMongodb) - .command(migrate) - .command(migrateToMongo) - .command(poorxy) - .command(processor) - .command(provider) - .command(reaper) - .command(storagePluginApk) - .command(storagePluginImage) - .command(storageS3) - .command(storageTemp) - .command(triproxy) - .command(websocket) - .demandCommand(1, 'Must provide a valid command.') - .help('h', 'Show help.') - .alias('h', 'help') - .version() - .alias('V', 'version') - .parse() + .strict() + .command(api) + .command(app) + .command(authLdap) + .command(authMock) + .command(authOauth2) + .command(authOpenid) + .command(authSaml2) + .command(groupsEngine) + .command(device) + .command(iosDevice) + .command(vncDevice) + .command(doctor) + .command(generateFakeDevice) + .command(generateFakeUser) + .command(generateFakeGroup) + .command(generateServiceUser) + .command(local) + .command(logMongodb) + .command(migrate) + .command(migrateToMongo) + .command(poorxy) + .command(processor) + .command(provider) + .command(reaper) + .command(storagePluginApk) + .command(storagePluginImage) + .command(storageS3) + .command(storageTemp) + .command(triproxy) + .command(websocket) + .demandCommand(1, 'Must provide a valid command.') + .help('h', 'Show help.') + .alias('h', 'help') + .version() + .alias('V', 'version') + .parse() diff --git a/lib/cli/ios-device/example b/lib/cli/ios-device/example index 434b772181..b7274c6791 100644 --- a/lib/cli/ios-device/example +++ b/lib/cli/ios-device/example @@ -1,6 +1,6 @@ stf ios-device \ - --serial udid \ - --device-name "" \ + --serial "346AEE38-F1C5-4FE2-BBCC-7B75786A44EE" \ + --device-name "Simulator iPhone 15" \ --host localhost \ --screen-port 7409 \ --mjpeg-port 9100 \ diff --git a/lib/cli/ios-device/index.js b/lib/cli/ios-device/index.js index 2950b2c0cf..e167610e92 100644 --- a/lib/cli/ios-device/index.js +++ b/lib/cli/ios-device/index.js @@ -5,148 +5,148 @@ export const builder = function(yargs) { return yargs .strict() .option('boot-complete-timeout', { - describe: 'How long to wait for boot to complete during device setup.' - , type: 'number' - , default: 60000 - }) + describe: 'How long to wait for boot to complete during device setup.' + , type: 'number' + , default: 60000 + }) .option('cleanup', { - describe: 'Attempt to reset the device between uses by uninstalling' + + describe: 'Attempt to reset the device between uses by uninstalling' + 'apps, resetting accounts and clearing caches. Does not do a perfect ' + 'job currently. Negate with --no-cleanup.' - , type: 'boolean' - , default: true - }) + , type: 'boolean' + , default: true + }) .option('connect-push', { - alias: 'p' - , describe: 'ZeroMQ PULL endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'p' + , describe: 'ZeroMQ PULL endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-sub', { - alias: 's' - , describe: 'ZeroMQ PUB endpoint to connect to.' - , array: true - , demand: true - }) + alias: 's' + , describe: 'ZeroMQ PUB endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-url-pattern', { - describe: 'The URL pattern to use for `adb connect`.' - , type: 'string' - , default: '${publicIp}:${publicPort}' - }) + describe: 'The URL pattern to use for `adb connect`.' + , type: 'string' + , default: '${publicIp}:${publicPort}' + }) .option('group-timeout', { - alias: 't' - , describe: 'Timeout in seconds for automatic release of inactive devices.' - , type: 'number' - , default: 900 - }) + alias: 't' + , describe: 'Timeout in seconds for automatic release of inactive devices.' + , type: 'number' + , default: 900 + }) .option('heartbeat-interval', { - describe: 'Send interval in milliseconds for heartbeat messages.' - , type: 'number' - , default: 10000 - }) + describe: 'Send interval in milliseconds for heartbeat messages.' + , type: 'number' + , default: 10000 + }) .option('lock-rotation', { - describe: 'Whether to lock rotation when devices are being used. ' + + describe: 'Whether to lock rotation when devices are being used. ' + 'Otherwise changing device orientation may not always work due to ' + 'sensitive sensors quickly or immediately reverting it back to the ' + 'physical orientation.' - , type: 'boolean' - }) + , type: 'boolean' + }) .option('provider', { - alias: 'n' - , describe: 'Name of the provider.' - , type: 'string' - , demand: true - }) + alias: 'n' + , describe: 'Name of the provider.' + , type: 'string' + , demand: true + }) .option('public-ip', { - describe: 'The IP or hostname to use in URLs.' - , type: 'string' - , demand: true - }) + describe: 'The IP or hostname to use in URLs.' + , type: 'string' + , demand: true + }) .option('screen-jpeg-quality', { - describe: 'The JPG quality to use for the screen.' - , type: 'number' - , default: process.env.SCREEN_JPEG_QUALITY || 80 - }) + describe: 'The JPG quality to use for the screen.' + , type: 'number' + , default: process.env.SCREEN_JPEG_QUALITY || 80 + }) .option('screen-ping-interval', { - describe: 'The interval at which to send ping messages to keep the ' + + describe: 'The interval at which to send ping messages to keep the ' + 'screen WebSocket alive.' - , type: 'number' - , default: 30000 - }) + , type: 'number' + , default: 30000 + }) .option('screen-port', { - describe: 'Port allocated to the screen WebSocket.' - , type: 'number' - , demand: true - }) + describe: 'Port allocated to the screen WebSocket.' + , type: 'number' + , demand: true + }) .option('screen-reset', { - describe: 'Go back to home screen and reset screen rotation ' + + describe: 'Go back to home screen and reset screen rotation ' + 'when user releases device. Negate with --no-screen-reset.' - , type: 'boolean' - , default: true - }) + , type: 'boolean' + , default: true + }) .option('screen-ws-url-pattern', { - describe: 'The URL pattern to use for the screen WebSocket.' - , type: 'string' - , default: 'ws://${publicIp}:${publicPort}' - }) + describe: 'The URL pattern to use for the screen WebSocket.' + , type: 'string' + , default: 'ws://${publicIp}:${publicPort}' + }) .option('serial', { - describe: 'The USB serial number of the device.' - , type: 'string' - , demand: true - }) + describe: 'The USB serial number of the device.' + , type: 'string' + , demand: true + }) .option('storage-url', { - alias: 'r' - , describe: 'The URL to the storage unit.' - , type: 'string' - , demand: true - }) + alias: 'r' + , describe: 'The URL to the storage unit.' + , type: 'string' + , demand: true + }) .option('connect-app-dealer', { - describe: 'App-side ZeroMQ DEALER endpoint to connect to.' - , array: true - , demand: true - }) + describe: 'App-side ZeroMQ DEALER endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-dev-dealer', { - describe: 'Device-side ZeroMQ DEALER endpoint to connect to.' - , array: true - , demand: true - }) + describe: 'Device-side ZeroMQ DEALER endpoint to connect to.' + , array: true + , demand: true + }) .option('wda-host', { - describe: 'iOS device host ip address where WDA is started.' - , type: 'string' - , demand: true - }) + describe: 'iOS device host ip address where WDA is started.' + , type: 'string' + , demand: true + }) .option('wda-port', { - describe: 'The port the WDA should run et.' - , type: 'number' - , default: 8100 - }) + describe: 'The port the WDA should run et.' + , type: 'number' + , default: 8100 + }) .option('mjpeg-port', { - describe: 'The port the WDA mjpeg is started.' - , type: 'number' - , default: 9001 - , demand: true - }) + describe: 'The port the WDA mjpeg is started.' + , type: 'number' + , default: 9001 + , demand: true + }) .option('udid-storage', { - describe: 'The path for ip information of devices' - , type: 'string' - , default: false - }) + describe: 'The path for ip information of devices' + , type: 'string' + , default: false + }) .option('iproxy', { - describe: 'If the option with iproxy is passed, use proxy connection with devices' - , type: 'boolean' - , default: false - }) + describe: 'If the option with iproxy is passed, use proxy connection with devices' + , type: 'boolean' + , default: false + }) .option('device-name', { - describe: 'Device name' - , type: 'string' - , default: 'Generic iOS Device' - }) + describe: 'Device name' + , type: 'string' + , default: 'Generic iOS Device' + }) .option('host', { - describe: 'Provider hostname.' - , type: 'string' - , demand: true - , default: '127.0.0.1' - }) + describe: 'Provider hostname.' + , type: 'string' + , demand: true + , default: '127.0.0.1' + }) } export const handler = function(argv) { return iosDevice({ diff --git a/lib/cli/ios-device/install-wda.sh b/lib/cli/ios-device/install-wda.sh new file mode 100755 index 0000000000..af36abb733 --- /dev/null +++ b/lib/cli/ios-device/install-wda.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -o pipefail + +cd "$(git rev-parse --show-toplevel)" || exit + + +if [ -n "$1" ]; then + deviceId=$1 +else + deviceId=$(idb list-targets | grep "device" | awk -F '|' '{ gsub(/ /, "", $0); print $2 }' | head -1) +fi + +if [ -n "$2" ]; then + deviceNum=$2 +else + deviceNum=0 +fi +deviceName=$(idb list-targets | grep "$deviceId" | awk -F '|' '{ gsub(/ $/, "", $1); print $1 }' | head -1) +echo "Using device $deviceId ($deviceName) with nmber $deviceNum" >&2 +pushd WebDriverAgent || exit +xcodebuild \ + -project WebDriverAgent.xcodeproj \ + -scheme WebDriverAgentRunner \ + -destination "id=$deviceId" \ + -allowProvisioningUpdates \ + build || exit + +xcodebuild \ + -project WebDriverAgent.xcodeproj \ + -scheme WebDriverAgentRunner \ + -destination "id=$deviceId" \ + -allowProvisioningUpdates \ + test diff --git a/lib/cli/ios-device/run-wda.sh b/lib/cli/ios-device/run-wda.sh index efec92ba06..e52b14730d 100755 --- a/lib/cli/ios-device/run-wda.sh +++ b/lib/cli/ios-device/run-wda.sh @@ -1,9 +1,4 @@ #!/bin/bash -# Example to run WDA with pymobiledevice3 - -echo "This script is a mere example. Please do not run it without modifications" -read -p "Press enter to continue" - set -o pipefail cd "$(git rev-parse --show-toplevel)" || exit @@ -15,28 +10,43 @@ else deviceId=$(idb list-targets | grep "device" | awk -F '|' '{ gsub(/ /, "", $0); print $2 }' | head -1) fi +if [ -z "$deviceId" ]; then + echo "Could not find device with empty" + exit +fi + +if [ -n "$2" ]; then + deviceNum=$2 +else + deviceNum=0 +fi +deviceName=$(idb list-targets | grep "$deviceId" | awk -F '|' '{ gsub(/ $/, "", $1); print $1 }' | head -1) + +echo "Using device $deviceId ($deviceName) with number $deviceNum" >&2 + trap exit EXIT -deviceName=$(idb list-targets | grep "$deviceId" | awk -F '|' '{ gsub(/ $/, "", $0); print $1 }' | head -1) -echo "Using device $deviceId ($deviceName)" >&2 pushd WebDriverAgent || exit + xcodebuild \ -project WebDriverAgent.xcodeproj \ -scheme WebDriverAgentRunner \ -destination "id=$deviceId" \ - build + -allowProvisioningUpdates \ + build || exit xcodebuild \ -project WebDriverAgent.xcodeproj \ -scheme WebDriverAgentRunner \ -destination "id=$deviceId" \ - test & + -allowProvisioningUpdates \ + test | tee /tmp/wdalog.txt & wdapid=$! -source /path/to/pymobiledevice3/venv -pymobiledevice3 usbmux forward 9100 9100 & +# source ~/Documents/pymobiledevice3/.venv/bin/activate +pymobiledevice3 usbmux forward --serial $deviceId 910$deviceNum 9100 & mjpegpid=$! -pymobiledevice3 usbmux forward 8100 8100 & +pymobiledevice3 usbmux forward --serial $deviceId 810$deviceNum 8100 & wdaproxypic=$! function exit() { @@ -46,22 +56,23 @@ function exit() { wait } -read -p "Press enter to continue" +gtail -n1 -f /tmp/wdalog.txt | grep -q "ServerURLHere" > /dev/null +# echo "Found line" -MONGODB_PORT_27017_TCP=mongodburl stf ios-device \ +cd "$(git rev-parse --show-toplevel)" || exit +MONGODB_PORT_27017_TCP=mongodb://10.211.0.2:27017 stf ios-device \ --serial "$deviceId" \ --device-name "$deviceName" \ --host localhost \ - --screen-port 18000 \ - --mjpeg-port 9100 \ - --provider providerName \ + --screen-port 1800$deviceNum \ + --mjpeg-port 910$deviceNum \ + --provider mightyworker \ --public-ip localhost \ - --screen-ws-url-pattern "wss://accessibleurlforfrontend/ios-device/18000/" \ - --storage-url http://storageurl/ \ - --connect-sub "tcp://ip:22003" \ - --connect-push "tcp://ip:22005" \ - --connect-app-dealer tcp://ip:22001 \ - --connect-dev-dealer tcp://ip:22004 \ + --screen-ws-url-pattern "ws://localhost:1800$deviceNum" \ + --storage-url http://localhost:7100/ \ + --connect-sub tcp://127.0.0.1:7250 \ + --connect-push tcp://127.0.0.1:7270 \ + --connect-app-dealer tcp://127.0.0.1:7160 \ + --connect-dev-dealer tcp://127.0.0.1:7260 \ --wda-host 127.0.0.1 \ - --wda-port 8100 - + --wda-port 810$deviceNum || exit diff --git a/lib/cli/ios-device/start-ios.sh b/lib/cli/ios-device/start-ios.sh index 271672dc5d..2d4c97e55a 100755 --- a/lib/cli/ios-device/start-ios.sh +++ b/lib/cli/ios-device/start-ios.sh @@ -1,11 +1,11 @@ #!/bin/bash stf ios-device \ - --serial ios-device \ - --device-name "ios device" \ + --serial "2BFBC585-B481-450B-914C-640D114BAAC7" \ + --device-name "iPhone 11" \ --host localhost \ --screen-port 7409 \ --mjpeg-port 9100 \ - --provider local-provider \ + --provider m-alzhanov \ --public-ip localhost \ --screen-ws-url-pattern "ws://localhost:7409" \ --storage-url http://localhost:7100/ \ diff --git a/lib/cli/local/index.js b/lib/cli/local/index.js index 50e207f153..1cb44d6db1 100644 --- a/lib/cli/local/index.js +++ b/lib/cli/local/index.js @@ -11,302 +11,302 @@ export const builder = function(yargs) { .env('STF_LOCAL') .strict() .option('adb-host', { - describe: 'The ADB server host.' - , type: 'string' - , default: '127.0.0.1' - }) + describe: 'The ADB server host.' + , type: 'string' + , default: '127.0.0.1' + }) .option('adb-port', { - describe: 'The ADB server port.' - , type: 'number' - , default: 5037 - }) + describe: 'The ADB server port.' + , type: 'number' + , default: 5037 + }) .option('ios-host', { - describe: 'The Ios server host.' - , type: 'string' - , default: '127.0.0.1' - }) + describe: 'The Ios server host.' + , type: 'string' + , default: '127.0.0.1' + }) .option('ios-port', { - describe: 'The Ios server port.' - , type: 'number' - , default: 5555 - }) + describe: 'The Ios server port.' + , type: 'number' + , default: 5555 + }) .option('allow-remote', { - alias: 'R' - , describe: 'Whether to allow remote devices in STF. Highly ' + + alias: 'R' + , describe: 'Whether to allow remote devices in STF. Highly ' + 'unrecommended due to almost unbelievable slowness on the ADB side ' + 'and duplicate device issues when used locally while having a ' + 'cable connected at the same time.' - , type: 'boolean' - }) + , type: 'boolean' + }) .option('api-port', { - describe: 'The port the api unit should run at.' - , type: 'number' - , default: 7106 - }) + describe: 'The port the api unit should run at.' + , type: 'number' + , default: 7106 + }) .option('app-port', { - describe: 'The port the app unit should run at.' - , type: 'number' - , default: 7105 - }) + describe: 'The port the app unit should run at.' + , type: 'number' + , default: 7105 + }) .option('app-url', { - describe: 'Publicly accessible URL to the app unit.' - , type: 'string' - }) + describe: 'Publicly accessible URL to the app unit.' + , type: 'string' + }) .option('auth-options', { - describe: 'JSON array of options to pass to the auth unit.' - , type: 'string' - , default: '[]' - }) + describe: 'JSON array of options to pass to the auth unit.' + , type: 'string' + , default: '[]' + }) .option('auth-port', { - describe: 'The port the auth unit should run at.' - , type: 'number' - , default: 7120 - }) + describe: 'The port the auth unit should run at.' + , type: 'number' + , default: 7120 + }) .option('auth-secret', { - describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + + describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + 'knows this token can freely enter the system if they want, so keep ' + 'it safe.' - , type: 'string' - , default: 'kute kittykat' - }) + , type: 'string' + , default: 'kute kittykat' + }) .option('auth-type', { - describe: 'The type of auth unit to start.' - , type: 'string' - , choices: ['mock', 'ldap', 'oauth2', 'saml2', 'openid'] - , default: 'mock' - }) + describe: 'The type of auth unit to start.' + , type: 'string' + , choices: ['mock', 'ldap', 'oauth2', 'saml2', 'openid'] + , default: 'mock' + }) .option('auth-url', { - alias: 'a' - , describe: 'URL to the auth unit.' - , type: 'string' - }) + alias: 'a' + , describe: 'URL to the auth unit.' + , type: 'string' + }) .option('bind-app-dealer', { - describe: 'The address to bind the app-side ZeroMQ DEALER endpoint to.' - , type: 'string' - , default: 'tcp://127.0.0.1:7112' - }) + describe: 'The address to bind the app-side ZeroMQ DEALER endpoint to.' + , type: 'string' + , default: 'tcp://127.0.0.1:7112' + }) .option('bind-app-pub', { - describe: 'The address to bind the app-side ZeroMQ PUB endpoint to.' - , type: 'string' - , default: 'tcp://127.0.0.1:7111' - }) + describe: 'The address to bind the app-side ZeroMQ PUB endpoint to.' + , type: 'string' + , default: 'tcp://127.0.0.1:7111' + }) .option('bind-app-pull', { - describe: 'The address to bind the app-side ZeroMQ PULL endpoint to.' - , type: 'string' - , default: 'tcp://127.0.0.1:7113' - }) + describe: 'The address to bind the app-side ZeroMQ PULL endpoint to.' + , type: 'string' + , default: 'tcp://127.0.0.1:7113' + }) .option('bind-dev-dealer', { - describe: 'The address to bind the device-side ZeroMQ DEALER endpoint to.' - , type: 'string' - , default: 'tcp://127.0.0.1:7115' - }) + describe: 'The address to bind the device-side ZeroMQ DEALER endpoint to.' + , type: 'string' + , default: 'tcp://127.0.0.1:7115' + }) .option('bind-dev-pub', { - describe: 'The address to bind the device-side ZeroMQ PUB endpoint to.' - , type: 'string' - , default: 'tcp://127.0.0.1:7114' - }) + describe: 'The address to bind the device-side ZeroMQ PUB endpoint to.' + , type: 'string' + , default: 'tcp://127.0.0.1:7114' + }) .option('bind-dev-pull', { - describe: 'The address to bind the device-side ZeroMQ PULL endpoint to.' - , type: 'string' - , default: 'tcp://127.0.0.1:7116' - }) + describe: 'The address to bind the device-side ZeroMQ PULL endpoint to.' + , type: 'string' + , default: 'tcp://127.0.0.1:7116' + }) .option('bind-ios-pub', { - describe: 'The address to bind the app-side ZeroMQ PUB endpoint to.' - , type: 'string' - , default: 'tcp://127.0.0.1:7401' - }) + describe: 'The address to bind the app-side ZeroMQ PUB endpoint to.' + , type: 'string' + , default: 'tcp://127.0.0.1:7401' + }) .option('bind-ios-pull', { - describe: 'The address to bind the app-side ZeroMQ PULL endpoint to.' - , type: 'string' - , default: 'tcp://127.0.0.1:7400' - }) + describe: 'The address to bind the app-side ZeroMQ PULL endpoint to.' + , type: 'string' + , default: 'tcp://127.0.0.1:7400' + }) .option('bind-temp-pull', { - descrive: 'The address to bind the app-side ZeroMQ PULL endpoint to.' - , type: 'string' - , default: 'tcp://127.0.0.1:7116' - }) + descrive: 'The address to bind the app-side ZeroMQ PULL endpoint to.' + , type: 'string' + , default: 'tcp://127.0.0.1:7116' + }) .option('bind-processor-pull', { - descrive: 'The address to bind the app-side ZeroMQ PULL endpoint to.' - , type: 'string' - , default: 'tcp://127.0.0.1:7117' - }) + descrive: 'The address to bind the app-side ZeroMQ PULL endpoint to.' + , type: 'string' + , default: 'tcp://127.0.0.1:7117' + }) .option('bind-temp-pub', { - describe: 'The address to bind the app-side ZeroMQ PUB endpoint to.' - , type: 'string' - , default: 'tcp://127.0.0.1:7407' - }) + describe: 'The address to bind the app-side ZeroMQ PUB endpoint to.' + , type: 'string' + , default: 'tcp://127.0.0.1:7407' + }) .option('cleanup', { - describe: 'Attempt to reset the device between uses by uninstalling' + + describe: 'Attempt to reset the device between uses by uninstalling' + 'apps, resetting accounts and clearing caches. Does not do a perfect ' + 'job currently. Negate with --no-cleanup.' - , type: 'boolean' - , default: true - }) + , type: 'boolean' + , default: true + }) .option('cleanup-disable-bluetooth', { - describe: 'Whether to disable Bluetooth during cleanup.' - , type: 'boolean' - , default: false - }) + describe: 'Whether to disable Bluetooth during cleanup.' + , type: 'boolean' + , default: false + }) .option('cleanup-bluetooth-bonds', { - describe: 'Whether to remove Bluetooth bonds during cleanup.' - , type: 'boolean' - , default: false - }) + describe: 'Whether to remove Bluetooth bonds during cleanup.' + , type: 'boolean' + , default: false + }) .option('group-timeout', { - alias: 't' - , describe: 'Timeout in seconds for automatic release of inactive devices.' - , type: 'number' - , default: 900 - }) + alias: 't' + , describe: 'Timeout in seconds for automatic release of inactive devices.' + , type: 'number' + , default: 900 + }) .option('lock-rotation', { - describe: 'Whether to lock rotation when devices are being used. ' + + describe: 'Whether to lock rotation when devices are being used. ' + 'Otherwise changing device orientation may not always work due to ' + 'sensitive sensors quickly or immediately reverting it back to the ' + 'physical orientation.' - , type: 'boolean' - }) + , type: 'boolean' + }) .option('mute-master', { - describe: 'Whether to mute master volume.' - , choices: ['always', 'inuse', 'never'] - , default: 'never' - , coerce: val => { - if (val === true) { - return 'inuse' // For backwards compatibility. - } - if (val === false) { - return 'never' // For backwards compatibility. + describe: 'Whether to mute master volume.' + , choices: ['always', 'inuse', 'never'] + , default: 'never' + , coerce: val => { + if (val === true) { + return 'inuse' // For backwards compatibility. + } + if (val === false) { + return 'never' // For backwards compatibility. + } + return val } - return val - } - }) + }) .option('port', { - alias: ['p', 'poorxy-port'] - , describe: 'The port STF should run at.' - , type: 'number' - , default: 7100 - }) + alias: ['p', 'poorxy-port'] + , describe: 'The port STF should run at.' + , type: 'number' + , default: 7100 + }) .option('provider', { - describe: 'An easily identifiable name for the UI and/or log output.' - , type: 'string' - , default: os.hostname() - }) + describe: 'An easily identifiable name for the UI and/or log output.' + , type: 'string' + , default: os.hostname() + }) .option('provider-ios', { - describe: 'An easily identifiable name for the UI and/or log output.' - , type: 'string' - , default: `${os.hostname()}-ios` - }) + describe: 'An easily identifiable name for the UI and/or log output.' + , type: 'string' + , default: `${os.hostname()}-ios` + }) .option('provider-max-port', { - describe: 'Highest port number for device workers to use.' - , type: 'number' - , default: 7700 - }) + describe: 'Highest port number for device workers to use.' + , type: 'number' + , default: 7700 + }) .option('provider-min-port', { - describe: 'Lowest port number for device workers to use.' - , type: 'number' - , default: 7400 - }) + describe: 'Lowest port number for device workers to use.' + , type: 'number' + , default: 7400 + }) .option('public-ip', { - describe: 'The IP or hostname to use in URLs.' - , type: 'string' - , default: 'localhost' - }) + describe: 'The IP or hostname to use in URLs.' + , type: 'string' + , default: 'localhost' + }) .option('screen-reset', { - describe: 'Go back to home screen and reset screen rotation ' + + describe: 'Go back to home screen and reset screen rotation ' + 'when user releases device. Negate with --no-screen-reset.' - , type: 'boolean' - , default: true - }) + , type: 'boolean' + , default: true + }) .option('screen-ws-url-pattern', { - describe: 'Publicly accessible URL pattern to use for the screen WebSocket.' - , type: 'string' - , default: 'ws://${publicIp}:${publicPort}' - }) + describe: 'Publicly accessible URL pattern to use for the screen WebSocket.' + , type: 'string' + , default: 'ws://${publicIp}:${publicPort}' + }) .option('serial', { - describe: 'Only use devices with these serial numbers.' - , type: 'array' - }) + describe: 'Only use devices with these serial numbers.' + , type: 'array' + }) .option('storage-options', { - describe: 'JSON array of options to pass to the storage unit.' - , type: 'string' - , default: '[]' - }) + describe: 'JSON array of options to pass to the storage unit.' + , type: 'string' + , default: '[]' + }) .option('storage-plugin-apk-port', { - describe: 'The port the storage-plugin-apk unit should run at.' - , type: 'number' - , default: 7104 - }) + describe: 'The port the storage-plugin-apk unit should run at.' + , type: 'number' + , default: 7104 + }) .option('storage-plugin-image-port', { - describe: 'The port the storage-plugin-image unit should run at.' - , type: 'number' - , default: 7103 - }) + describe: 'The port the storage-plugin-image unit should run at.' + , type: 'number' + , default: 7103 + }) .option('storage-port', { - describe: 'The port the storage unit should run at.' - , type: 'number' - , default: 7102 - }) + describe: 'The port the storage unit should run at.' + , type: 'number' + , default: 7102 + }) .option('storage-type', { - describe: 'The type of storage unit to start.' - , type: 'string' - , choices: ['temp', 's3'] - , default: 'temp' - }) + describe: 'The type of storage unit to start.' + , type: 'string' + , choices: ['temp', 's3'] + , default: 'temp' + }) .option('user-profile-url', { - describe: 'URL to external user profile page' - , type: 'string' - }) + describe: 'URL to external user profile page' + , type: 'string' + }) .option('vnc-initial-size', { - describe: 'The initial size to use for the experimental VNC server.' - , type: 'string' - , default: '600x800' - , coerce: function(val) { - return val.split('x').map(Number) - } - }) + describe: 'The initial size to use for the experimental VNC server.' + , type: 'string' + , default: '600x800' + , coerce: function(val) { + return val.split('x').map(Number) + } + }) .option('websocket-port', { - describe: 'The port the websocket unit should run at.' - , type: 'number' - , default: 7110 - }) + describe: 'The port the websocket unit should run at.' + , type: 'number' + , default: 7110 + }) .option('need-scrcpy', { - describe: 'Need using Scrcpy instead Minicap for screenshots.' - , type: 'boolean' - , default: false - }) + describe: 'Need using Scrcpy instead Minicap for screenshots.' + , type: 'boolean' + , default: false + }) .option('wda-host', { - describe: 'iOS device host ip address where WDA is started.' - , type: 'string' - , default: '192.168.88.78' - }) + describe: 'iOS device host ip address where WDA is started.' + , type: 'string' + , default: '192.168.88.78' + }) .option('wda-port', { - describe: 'The port the WDA should run et.' - , type: 'number' - , default: 20001 - }) + describe: 'The port the WDA should run et.' + , type: 'number' + , default: 20001 + }) .option('mjpeg-port', { - describe: 'The port the WDA mjpeg is started.' - , type: 'number' - , default: 20002 - }) + describe: 'The port the WDA mjpeg is started.' + , type: 'number' + , default: 20002 + }) .option('udid-storage', { - describe: 'The path for ip information of devoces' - , type: 'string' - , default: false - }) + describe: 'The path for ip information of devoces' + , type: 'string' + , default: false + }) .option('iproxy', { - describe: 'If the option with iproxy is passed, use proxy connection with devices' - , type: 'boolean' - , default: false - }) + describe: 'If the option with iproxy is passed, use proxy connection with devices' + , type: 'boolean' + , default: false + }) .option('websocket-url', { - describe: 'Publicly accessible URL to the websocket unit.' - , type: 'string' - }) + describe: 'Publicly accessible URL to the websocket unit.' + , type: 'string' + }) .option('url-without-adb-port', { - describe: 'If there isnt adbPort in DB use baseUrl.' - , type: 'boolean' - , default: true - }) + describe: 'If there isnt adbPort in DB use baseUrl.' + , type: 'boolean' + , default: true + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_LOCAL_` (e.g. ' + @@ -366,7 +366,7 @@ export const handler = function(argv) { , '--need-scrcpy', argv.needScrcpy , '--screen-ws-url-pattern', argv.screenWsUrlPattern , '--url-without-adb-port', argv.urlWithoutAdbPort - ] + ] .concat(argv.allowRemote ? ['--allow-remote'] : []) .concat(argv.lockRotation ? ['--lock-rotation'] : []) .concat(!argv.cleanup ? ['--no-cleanup'] : []) @@ -487,15 +487,15 @@ export const handler = function(argv) { }) return Promise.all(procs) .then(function() { - process.exit(0) - }) + process.exit(0) + }) .catch(function(err) { - log.fatal('Child process had an error', err.stack) - return shutdown() - .then(function() { - process.exit(1) + log.fatal('Child process had an error', err.stack) + return shutdown() + .then(function() { + process.exit(1) + }) }) - }) } return procutil.fork(path.join(import.meta.dirname, '../index.js'), ['migrate']) .done(run) diff --git a/lib/cli/log-mongodb/index.js b/lib/cli/log-mongodb/index.js index 57c51e9822..1820d69414 100644 --- a/lib/cli/log-mongodb/index.js +++ b/lib/cli/log-mongodb/index.js @@ -7,17 +7,17 @@ export const builder = function(yargs) { .env('STF_LOG_MONGODB') .strict() .option('connect-sub', { - alias: 's' - , describe: 'App-side ZeroMQ PUB endpoint to connect to.' - , array: true - , demand: true - }) + alias: 's' + , describe: 'App-side ZeroMQ PUB endpoint to connect to.' + , array: true + , demand: true + }) .option('priority', { - alias: 'p' - , describe: 'Minimum log level.' - , type: 'number' - , default: logger.Level.IMPORTANT - }) + alias: 'p' + , describe: 'Minimum log level.' + , type: 'number' + , default: logger.Level.IMPORTANT + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_LOG_MONGODB_` (e.g. ' + diff --git a/lib/cli/migrate-to-mongo/index.js b/lib/cli/migrate-to-mongo/index.js index 67adca79e0..c1267abbfa 100644 --- a/lib/cli/migrate-to-mongo/index.js +++ b/lib/cli/migrate-to-mongo/index.js @@ -1,7 +1,7 @@ import logger from '../../util/logger.js' import rdb from './rdb.js' import r from 'rethinkdb' -import * as db from '../../db/index.mjs' +import * as db from '../../db/index.js' import Promise from 'bluebird' export const command = 'migrate-to-mongo' export const describe = 'Migrates the database to mongoDB.' @@ -18,19 +18,19 @@ export const handler = function() { return rdb.run(r.table(table)).then(cursor => { return cursor.toArray() .then(tableData => { - return client.collection(table).insertMany(tableData).then(stats => { - log.info('Migration result for ' + table + ' inserted ' + stats.insertedCount) + return client.collection(table).insertMany(tableData).then(stats => { + log.info('Migration result for ' + table + ' inserted ' + stats.insertedCount) + }) }) - }) .catch(e => { - log.error('Error while migrating ' + table + ' ' + e) - }) + log.error('Error while migrating ' + table + ' ' + e) + }) }) })) .then(() => { - log.info('Migration ended') - process.exit(0) - }) + log.info('Migration ended') + process.exit(0) + }) }) } performMigrate() diff --git a/lib/cli/migrate-to-mongo/rdb.js b/lib/cli/migrate-to-mongo/rdb.js index d92e7ae5ea..e96bd17bbf 100644 --- a/lib/cli/migrate-to-mongo/rdb.js +++ b/lib/cli/migrate-to-mongo/rdb.js @@ -16,25 +16,25 @@ function connect() { } return srv.resolve(options.url) .then(function(records) { - function next() { - let record = records.shift() - if (!record) { - throw new Error('No hosts left to try') + function next() { + let record = records.shift() + if (!record) { + throw new Error('No hosts left to try') + } + log.info('Connecting to %s:%d', record.name, record.port) + return r.connect({ + host: record.name + , port: record.port + , db: options.db + , authKey: options.authKey + }) + .catch(r.Error.RqlDriverError, function() { + log.info('Unable to connect to %s:%d', record.name, record.port) + return next() + }) } - log.info('Connecting to %s:%d', record.name, record.port) - return r.connect({ - host: record.name - , port: record.port - , db: options.db - , authKey: options.authKey - }) - .catch(r.Error.RqlDriverError, function() { - log.info('Unable to connect to %s:%d', record.name, record.port) - return next() - }) - } - return next() - }) + return next() + }) } // Export connection as a Promise db.connect = function() { @@ -50,24 +50,24 @@ db.connect = function() { function createConnection() { return connect() .then(function(conn) { - connection = conn - conn.on('close', function closeListener() { - log.warn('Connection closed') - connection = null - conn.removeListener('close', closeListener) - if (!goingDown) { - createConnection() - } - }) - queue.splice(0).forEach(function(resolver) { - resolver.resolve(conn) + connection = conn + conn.on('close', function closeListener() { + log.warn('Connection closed') + connection = null + conn.removeListener('close', closeListener) + if (!goingDown) { + createConnection() + } + }) + queue.splice(0).forEach(function(resolver) { + resolver.resolve(conn) + }) + return conn }) - return conn - }) .catch(function(err) { - log.fatal(err.message) - lifecycle.fatal() - }) + log.fatal(err.message) + lifecycle.fatal() + }) } createConnection() return new Promise(function(resolve, reject) { diff --git a/lib/cli/migrate/index.js b/lib/cli/migrate/index.js index e3c8a82775..024f60a402 100644 --- a/lib/cli/migrate/index.js +++ b/lib/cli/migrate/index.js @@ -3,9 +3,9 @@ **/ import logger from '../../util/logger.js' const log = logger.createLogger('cli:migrate') -import db from '../../db/index.mjs' -import dbapi from '../../db/api.mjs' -import apiutil from '../../util/apiutil.js' +import db from '../../db/index.js' +import * as dbapi from '../../db/api.js' +import * as apiutil from '../../util/apiutil.js' import Promise from 'bluebird' @@ -14,46 +14,46 @@ export const command = 'migrate' export const describe = 'Migrates the database to the latest version.' export const builder = function(yargs) { - return yargs + return yargs } export const handler = function() { - return db.setup() - .then(function() { - return new Promise(function(resolve, reject) { - setTimeout(function() { - return dbapi.getGroupByIndex(apiutil.ROOT, 'privilege').then(function(group) { - if (!group) { - const env = { - STF_ROOT_GROUP_NAME: 'Common' - , STF_ADMIN_NAME: 'administrator' - , STF_ADMIN_EMAIL: 'administrator@fakedomain.com' - } - for (const i in env) { - if (process.env[i]) { - env[i] = process.env[i] - } - } - return dbapi.createBootStrap(env) - } - return group - }) - .then(function() { - resolve(true) - }) - .catch(function(err) { - reject(err) - }) - }, 1000) - }) - }) - .catch(function(err) { - log.fatal('Migration had an error:', err.stack) - process.exit(1) - }) - .finally(function() { - process.exit(0) - }) + return db.setup() + .then(function() { + return new Promise(function(resolve, reject) { + setTimeout(function() { + return dbapi.getGroupByIndex(apiutil.ROOT, 'privilege').then(function(group) { + if (!group) { + const env = { + STF_ROOT_GROUP_NAME: 'Common' + , STF_ADMIN_NAME: 'administrator' + , STF_ADMIN_EMAIL: 'administrator@fakedomain.com' + } + for (const i in env) { + if (process.env[i]) { + env[i] = process.env[i] + } + } + return dbapi.createBootStrap(env) + } + return group + }) + .then(function() { + resolve(true) + }) + .catch(function(err) { + reject(err) + }) + }, 1000) + }) + }) + .catch(function(err) { + log.fatal('Migration had an error:', err.stack) + process.exit(1) + }) + .finally(function() { + process.exit(0) + }) } export * as default from './index.js' diff --git a/lib/cli/poorxy/index.js b/lib/cli/poorxy/index.js index a0c484552b..119c7ead30 100644 --- a/lib/cli/poorxy/index.js +++ b/lib/cli/poorxy/index.js @@ -5,51 +5,51 @@ export const builder = function(yargs) { .strict() .env('STF_POORXY') .option('api-url', { - alias: 'i' - , describe: 'URL to the api unit.' - , type: 'string' - , demand: true - }) + alias: 'i' + , describe: 'URL to the api unit.' + , type: 'string' + , demand: true + }) .option('app-url', { - alias: 'u' - , describe: 'URL to the app unit.' - , type: 'string' - , demand: true - }) + alias: 'u' + , describe: 'URL to the app unit.' + , type: 'string' + , demand: true + }) .option('auth-url', { - alias: 'a' - , describe: 'URL to the auth unit.' - , type: 'string' - , demand: true - }) + alias: 'a' + , describe: 'URL to the auth unit.' + , type: 'string' + , demand: true + }) .option('port', { - alias: 'p' - , describe: 'The port to launch poorxy on.' - , type: 'number' - , default: process.env.PORT || 7100 - }) + alias: 'p' + , describe: 'The port to launch poorxy on.' + , type: 'number' + , default: process.env.PORT || 7100 + }) .option('storage-plugin-apk-url', { - describe: 'URL to the APK storage plugin unit.' - , type: 'string' - , demand: true - }) + describe: 'URL to the APK storage plugin unit.' + , type: 'string' + , demand: true + }) .option('storage-plugin-image-url', { - describe: 'URL to the image storage plugin unit.' - , type: 'string' - , demand: true - }) + describe: 'URL to the image storage plugin unit.' + , type: 'string' + , demand: true + }) .option('storage-url', { - alias: 'r' - , describe: 'URL to the storage unit.' - , type: 'string' - , demand: true - }) + alias: 'r' + , describe: 'URL to the storage unit.' + , type: 'string' + , demand: true + }) .option('websocket-url', { - alias: 'w' - , describe: 'URL to the websocket unit.' - , type: 'string' - , demand: true - }) + alias: 'w' + , describe: 'URL to the websocket unit.' + , type: 'string' + , demand: true + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_POORXY_` (e.g. ' + diff --git a/lib/cli/processor/index.js b/lib/cli/processor/index.js index e297a89903..464e06dd17 100644 --- a/lib/cli/processor/index.js +++ b/lib/cli/processor/index.js @@ -7,28 +7,28 @@ export const builder = function(yargs) { .env('STF_PROCESSOR') .strict() .option('connect-app-dealer', { - alias: 'a' - , describe: 'App-side ZeroMQ DEALER endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'a' + , describe: 'App-side ZeroMQ DEALER endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-dev-dealer', { - alias: 'd' - , describe: 'Device-side ZeroMQ DEALER endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'd' + , describe: 'Device-side ZeroMQ DEALER endpoint to connect to.' + , array: true + , demand: true + }) .option('name', { - describe: 'An easily identifiable name for log output.' - , type: 'string' - , default: os.hostname() - }) + describe: 'An easily identifiable name for log output.' + , type: 'string' + , default: os.hostname() + }) .option('public-ip', { - alias: 'pi' - , description: 'Defined public ip for stf' - , type: 'string' - , default: 'localhost' - }) + alias: 'pi' + , description: 'Defined public ip for stf' + , type: 'string' + , default: 'localhost' + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_PROCESSOR_` (e.g. ' + diff --git a/lib/cli/provider/index.js b/lib/cli/provider/index.js index 45a52bc33d..dcb9cc0777 100644 --- a/lib/cli/provider/index.js +++ b/lib/cli/provider/index.js @@ -10,194 +10,194 @@ export const builder = function(yargs) { .strict() .env('STF_PROVIDER') .option('adb-host', { - describe: 'The ADB server host.' - , type: 'string' - , default: '127.0.0.1' - }) + describe: 'The ADB server host.' + , type: 'string' + , default: '127.0.0.1' + }) .option('adb-port', { - describe: 'The ADB server port.' - , type: 'number' - , default: 5037 - }) + describe: 'The ADB server port.' + , type: 'number' + , default: 5037 + }) .option('allow-remote', { - alias: 'R' - , describe: 'Whether to allow remote devices in STF. Highly ' + + alias: 'R' + , describe: 'Whether to allow remote devices in STF. Highly ' + 'unrecommended due to almost unbelievable slowness on the ADB side ' + 'and duplicate device issues when used locally while having a ' + 'cable connected at the same time.' - , type: 'boolean' - }) + , type: 'boolean' + }) .option('boot-complete-timeout', { - describe: 'How long to wait for boot to complete during device setup.' - , type: 'number' - , default: 60000 - }) + describe: 'How long to wait for boot to complete during device setup.' + , type: 'number' + , default: 60000 + }) .option('cleanup', { - describe: 'Attempt to reset the device between uses by uninstalling' + + describe: 'Attempt to reset the device between uses by uninstalling' + 'apps, resetting accounts and clearing caches. Does not do a perfect ' + 'job currently. Negate with --no-cleanup.' - , type: 'boolean' - , default: true - }) + , type: 'boolean' + , default: true + }) .option('cleanup-disable-bluetooth', { - describe: 'Whether to disable Bluetooth during cleanup.' - , type: 'boolean' - , default: false - }) + describe: 'Whether to disable Bluetooth during cleanup.' + , type: 'boolean' + , default: false + }) .option('cleanup-bluetooth-bonds', { - describe: 'Whether to remove Bluetooth bonds during cleanup.' - , type: 'boolean' - , default: false - }) + describe: 'Whether to remove Bluetooth bonds during cleanup.' + , type: 'boolean' + , default: false + }) .option('connect-push', { - alias: 'p' - , describe: 'Device-side ZeroMQ PULL endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'p' + , describe: 'Device-side ZeroMQ PULL endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-sub', { - alias: 's' - , describe: 'Device-side ZeroMQ PUB endpoint to connect to.' - , array: true - , demand: true - }) + alias: 's' + , describe: 'Device-side ZeroMQ PUB endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-url-pattern', { - describe: 'The URL pattern to use for `adb connect`.' - , type: 'string' - , default: '${publicIp}:${publicPort}' - }) + describe: 'The URL pattern to use for `adb connect`.' + , type: 'string' + , default: '${publicIp}:${publicPort}' + }) .option('group-timeout', { - alias: 't' - , describe: 'Timeout in seconds for automatic release of inactive devices.' - , type: 'number' - , default: 900 - }) + alias: 't' + , describe: 'Timeout in seconds for automatic release of inactive devices.' + , type: 'number' + , default: 900 + }) .option('heartbeat-interval', { - describe: 'Send interval in milliseconds for heartbeat messages.' - , type: 'number' - , default: 10000 - }) + describe: 'Send interval in milliseconds for heartbeat messages.' + , type: 'number' + , default: 10000 + }) .option('lock-rotation', { - describe: 'Whether to lock rotation when devices are being used. ' + + describe: 'Whether to lock rotation when devices are being used. ' + 'Otherwise changing device orientation may not always work due to ' + 'sensitive sensors quickly or immediately reverting it back to the ' + 'physical orientation.' - , type: 'boolean' - }) + , type: 'boolean' + }) .option('max-port', { - describe: 'Highest port number for device workers to use.' - , type: 'number' - , default: 7700 - }) + describe: 'Highest port number for device workers to use.' + , type: 'number' + , default: 7700 + }) .option('min-port', { - describe: 'Lowest port number for device workers to use.' - , type: 'number' - , default: 7400 - }) + describe: 'Lowest port number for device workers to use.' + , type: 'number' + , default: 7400 + }) .option('mute-master', { - describe: 'Whether to mute master volume.' - , choices: ['always', 'inuse', 'never'] - , default: 'never' - , coerce: val => { - if (val === true) { - return 'inuse' // For backwards compatibility. - } - if (val === false) { - return 'never' // For backwards compatibility. + describe: 'Whether to mute master volume.' + , choices: ['always', 'inuse', 'never'] + , default: 'never' + , coerce: val => { + if (val === true) { + return 'inuse' // For backwards compatibility. + } + if (val === false) { + return 'never' // For backwards compatibility. + } + return val } - return val - } - }) + }) .option('name', { - alias: 'n' - , describe: 'An easily identifiable name for the UI and/or log output.' - , type: 'string' - , default: os.hostname() - }) + alias: 'n' + , describe: 'An easily identifiable name for the UI and/or log output.' + , type: 'string' + , default: os.hostname() + }) .option('public-ip', { - describe: 'The IP or hostname to use in URLs.' - , type: 'string' - , default: ip() - }) + describe: 'The IP or hostname to use in URLs.' + , type: 'string' + , default: ip() + }) .option('screen-frame-rate', { - describe: 'The frame rate (frames/s) to be used for screen transport on the network. ' + + describe: 'The frame rate (frames/s) to be used for screen transport on the network. ' + 'Float value must be > 0.0 otherwise the default behavior is kept' - , type: 'number' - , default: process.env.SCREEN_FRAME_RATE || 20 - }) + , type: 'number' + , default: process.env.SCREEN_FRAME_RATE || 20 + }) .option('screen-jpeg-quality', { - describe: 'The JPG quality to use for the screen.' - , type: 'number' - , default: process.env.SCREEN_JPEG_QUALITY || 80 - }) + describe: 'The JPG quality to use for the screen.' + , type: 'number' + , default: process.env.SCREEN_JPEG_QUALITY || 80 + }) .option('screen-grabber', { - describe: 'The tool to be used for screen capture. ' + + describe: 'The tool to be used for screen capture. ' + 'Value must be either: minicap-bin (default) or minicap-apk' - , type: 'string' - , default: process.env.SCREEN_GRABBER || 'minicap-bin' - }) + , type: 'string' + , default: process.env.SCREEN_GRABBER || 'minicap-bin' + }) .option('screen-ping-interval', { - describe: 'The interval at which to send ping messages to keep the ' + + describe: 'The interval at which to send ping messages to keep the ' + 'screen WebSocket alive.' - , type: 'number' - , default: 30000 - }) + , type: 'number' + , default: 30000 + }) .option('screen-reset', { - describe: 'Go back to home screen and reset screen rotation ' + + describe: 'Go back to home screen and reset screen rotation ' + 'when user releases device. Negate with --no-screen-reset.' - , type: 'boolean' - , default: true - }) + , type: 'boolean' + , default: true + }) .option('screen-ws-url-pattern', { - describe: 'The URL pattern to use for the screen WebSocket.' - , type: 'string' - , default: 'ws://${publicIp}:${publicPort}' // using ws because we can't use wss on localhost - }) + describe: 'The URL pattern to use for the screen WebSocket.' + , type: 'string' + , default: 'ws://${publicIp}:${publicPort}' // using ws because we can't use wss on localhost + }) .option('storage-url', { - alias: 'r' - , describe: 'The URL to the storage unit.' - , type: 'string' - , demand: true - }) + alias: 'r' + , describe: 'The URL to the storage unit.' + , type: 'string' + , demand: true + }) .option('vnc-initial-size', { - describe: 'The initial size to use for the experimental VNC server.' - , type: 'string' - , default: '600x800' - , coerce: function(val) { - return val.split('x').map(Number) - } - }) + describe: 'The initial size to use for the experimental VNC server.' + , type: 'string' + , default: '600x800' + , coerce: function(val) { + return val.split('x').map(Number) + } + }) .option('need-scrcpy', { - describe: 'Need using Scrcpy instead Minicap for screenshots.' - , type: 'boolean' - , default: false - }) + describe: 'Need using Scrcpy instead Minicap for screenshots.' + , type: 'boolean' + , default: false + }) .option('vnc-port', { - describe: 'Port allocated to VNC connections.' - , type: 'number' - , demand: true - , default: 5900 - }) + describe: 'Port allocated to VNC connections.' + , type: 'number' + , demand: true + , default: 5900 + }) .option('device-name', { - describe: 'Device name' - , type: 'string' - , default: false - }) + describe: 'Device name' + , type: 'string' + , default: false + }) .option('device-type', { - describe: 'Device type' - , type: 'string' - , default: 'Android' - }) + describe: 'Device type' + , type: 'string' + , default: 'Android' + }) .option('host', { - describe: 'Provider hostname.' - , type: 'string' - , demand: true - , default: '127.0.0.1' - }) + describe: 'Provider hostname.' + , type: 'string' + , demand: true + , default: '127.0.0.1' + }) .option('url-without-adb-port', { - describe: 'If there isnt adbPort in DB use baseUrl.' - , type: 'boolean' - , default: true + describe: 'If there isnt adbPort in DB use baseUrl.' + , type: 'boolean' + , default: true }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + @@ -248,13 +248,13 @@ export const handler = function(argv) { , '--need-scrcpy', argv.needScrcpy , '--host', argv.host , '--url-without-adb-port', argv.urlWithoutAdbPort - ] + ] .concat(argv.connectSub.reduce(function(all, val) { - return all.concat(['--connect-sub', val]) - }, [])) + return all.concat(['--connect-sub', val]) + }, [])) .concat(argv.connectPush.reduce(function(all, val) { - return all.concat(['--connect-push', val]) - }, [])) + return all.concat(['--connect-push', val]) + }, [])) .concat(argv.lockRotation ? ['--lock-rotation'] : []) .concat(!argv.cleanup ? ['--no-cleanup'] : []) .concat(argv.cleanupDisableBluetooth ? ['--cleanup-disable-bluetooth'] : []) diff --git a/lib/cli/reaper/index.js b/lib/cli/reaper/index.js index 787792a36d..f12686d579 100644 --- a/lib/cli/reaper/index.js +++ b/lib/cli/reaper/index.js @@ -7,29 +7,29 @@ export const builder = function(yargs) { .env('STF_REAPER') .strict() .option('connect-push', { - alias: 'p' - , describe: 'Device-side ZeroMQ PULL endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'p' + , describe: 'Device-side ZeroMQ PULL endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-sub', { - alias: 's' - , describe: 'App-side ZeroMQ PUB endpoint to connect to.' - , array: true - , demand: true - }) + alias: 's' + , describe: 'App-side ZeroMQ PUB endpoint to connect to.' + , array: true + , demand: true + }) .option('heartbeat-timeout', { - alias: 't' - , describe: 'Consider devices with heartbeat older than the timeout ' + + alias: 't' + , describe: 'Consider devices with heartbeat older than the timeout ' + 'value dead. Given in milliseconds.' - , type: 'number' - , default: 30000 - }) + , type: 'number' + , default: 30000 + }) .option('name', { - describe: 'An easily identifiable name for log output.' - , type: 'string' - , default: os.hostname() - }) + describe: 'An easily identifiable name for log output.' + , type: 'string' + , default: os.hostname() + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_REAPER_` (e.g. ' + diff --git a/lib/cli/storage-plugin-apk/index.js b/lib/cli/storage-plugin-apk/index.js index 19a5c62fe1..a3026a802b 100644 --- a/lib/cli/storage-plugin-apk/index.js +++ b/lib/cli/storage-plugin-apk/index.js @@ -7,37 +7,37 @@ export const builder = function(yargs) { .env('STF_STORAGE_PLUGIN_APK') .strict() .option('port', { - alias: 'p' - , describe: 'The port to bind to.' - , type: 'number' - , default: process.env.PORT || 7100 - }) + alias: 'p' + , describe: 'The port to bind to.' + , type: 'number' + , default: process.env.PORT || 7100 + }) .option('storage-url', { - alias: 'r' - , describe: 'URL to the storage unit.' - , type: 'string' - , demand: true - }) + alias: 'r' + , describe: 'URL to the storage unit.' + , type: 'string' + , demand: true + }) .option('cache-dir', { - describe: 'The location where to cache APK files.' - , type: 'string' - , default: os.tmpdir() - }) + describe: 'The location where to cache APK files.' + , type: 'string' + , default: os.tmpdir() + }) .option('secret', { - alias: 's' - , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + + alias: 's' + , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + 'knows this token can freely enter the system if they want, so keep ' + 'it safe.' - , type: 'string' - , default: process.env.SECRET - , demand: true - }) + , type: 'string' + , default: process.env.SECRET + , demand: true + }) .option('ssid', { - alias: 'i' - , describe: 'The name of the session ID cookie.' - , type: 'string' - , default: process.env.SSID || 'ssid' - }) + alias: 'i' + , describe: 'The name of the session ID cookie.' + , type: 'string' + , default: process.env.SSID || 'ssid' + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_STORAGE_PLUGIN_APK_` (e.g. ' + diff --git a/lib/cli/storage-plugin-image/index.js b/lib/cli/storage-plugin-image/index.js index 5c0031c143..45fa65ca1c 100644 --- a/lib/cli/storage-plugin-image/index.js +++ b/lib/cli/storage-plugin-image/index.js @@ -7,42 +7,42 @@ export const builder = function(yargs) { .env('STF_STORAGE_PLUGIN_IMAGE') .strict() .option('concurrency', { - alias: 'c' - , describe: 'Maximum number of simultaneous transformations.' - , type: 'number' - }) + alias: 'c' + , describe: 'Maximum number of simultaneous transformations.' + , type: 'number' + }) .option('port', { - alias: 'p' - , describe: 'The port to bind to.' - , type: 'number' - , default: process.env.PORT || 7100 - }) + alias: 'p' + , describe: 'The port to bind to.' + , type: 'number' + , default: process.env.PORT || 7100 + }) .option('storage-url', { - alias: 'r' - , describe: 'URL to the storage unit.' - , type: 'string' - , demand: true - }) + alias: 'r' + , describe: 'URL to the storage unit.' + , type: 'string' + , demand: true + }) .option('cache-dir', { - describe: 'The location where to cache images.' - , type: 'string' - , default: os.tmpdir() - }) + describe: 'The location where to cache images.' + , type: 'string' + , default: os.tmpdir() + }) .option('secret', { - alias: 's' - , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + + alias: 's' + , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + 'knows this token can freely enter the system if they want, so keep ' + 'it safe.' - , type: 'string' - , default: process.env.SECRET - , demand: true - }) + , type: 'string' + , default: process.env.SECRET + , demand: true + }) .option('ssid', { - alias: 'i' - , describe: 'The name of the session ID cookie.' - , type: 'string' - , default: process.env.SSID || 'ssid' - }) + alias: 'i' + , describe: 'The name of the session ID cookie.' + , type: 'string' + , default: process.env.SSID || 'ssid' + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_STORAGE_PLUGIN_IMAGE_` (e.g. ' + diff --git a/lib/cli/storage-s3/index.js b/lib/cli/storage-s3/index.js index 40d441bc9e..fb8f2a68c5 100644 --- a/lib/cli/storage-s3/index.js +++ b/lib/cli/storage-s3/index.js @@ -6,32 +6,32 @@ export const builder = function(yargs) { .env('STF_STORAGE_S3') .strict() .option('bucket', { - describe: 'S3 bucket name.' - , type: 'string' - , demand: true - }) + describe: 'S3 bucket name.' + , type: 'string' + , demand: true + }) .option('endpoint', { - describe: 'S3 bucket endpoint.' - , type: 'string' - , demand: true - }) + describe: 'S3 bucket endpoint.' + , type: 'string' + , demand: true + }) .option('max-file-size', { - describe: 'Maximum file size to allow for uploads. Note that nginx ' + + describe: 'Maximum file size to allow for uploads. Note that nginx ' + 'may have a separate limit, meaning you should change both.' - , type: 'number' - , default: 1 * 1024 * 1024 * 1024 - }) + , type: 'number' + , default: 1 * 1024 * 1024 * 1024 + }) .option('port', { - alias: 'p' - , describe: 'The port to bind to.' - , type: 'number' - , default: process.env.PORT || 7100 - }) + alias: 'p' + , describe: 'The port to bind to.' + , type: 'number' + , default: process.env.PORT || 7100 + }) .option('profile', { - describe: 'AWS credentials profile name.' - , type: 'string' - , demand: true - }) + describe: 'AWS credentials profile name.' + , type: 'string' + , demand: true + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_STORAGE_S3_` (e.g. ' + diff --git a/lib/cli/storage-temp/index.js b/lib/cli/storage-temp/index.js index d4ee44b4ff..f47ca56d42 100644 --- a/lib/cli/storage-temp/index.js +++ b/lib/cli/storage-temp/index.js @@ -7,93 +7,93 @@ export const builder = function(yargs) { .env('STF_STORAGE_TEMP') .strict() .option('max-file-size', { - describe: 'Maximum file size to allow for uploads. Note that nginx ' + + describe: 'Maximum file size to allow for uploads. Note that nginx ' + 'may have a separate limit, meaning you should change both.' - , type: 'number' - , default: 1 * 1024 * 1024 * 1024 - }) + , type: 'number' + , default: 1 * 1024 * 1024 * 1024 + }) .option('port', { - alias: 'p' - , describe: 'The port to bind to.' - , type: 'number' - , default: process.env.PORT || 7100 - }) + alias: 'p' + , describe: 'The port to bind to.' + , type: 'number' + , default: process.env.PORT || 7100 + }) .option('connect-push', { - describe: 'Device-side ZeroMQ PULL endpoint to connect to.' - , array: true - , demand: true - }) + describe: 'Device-side ZeroMQ PULL endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-sub', { - describe: 'Device-side ZeroMQ PULL endpoint to connect to.' - , array: true - , demand: true - }) + describe: 'Device-side ZeroMQ PULL endpoint to connect to.' + , array: true + , demand: true + }) .option('save-dir', { - describe: 'The location where files are saved to.' - , type: 'string' - , default: os.tmpdir() - }) + describe: 'The location where files are saved to.' + , type: 'string' + , default: os.tmpdir() + }) .option('bundletool-path', { - describe: 'The path to bundletool binary.' - , type: 'string' - , default: '/app/bundletool/bundletool.jar' - }) + describe: 'The path to bundletool binary.' + , type: 'string' + , default: '/app/bundletool/bundletool.jar' + }) .option('ks', { - describe: 'The name of the keystore to sign APKs built from AAB.' - , type: 'string' - , default: 'openstf' - }) + describe: 'The name of the keystore to sign APKs built from AAB.' + , type: 'string' + , default: 'openstf' + }) .option('ks-key-alias', { - describe: 'Indicates the alias to be used in the future to refer to the keystore.' - , type: 'string' - , default: 'mykey' - }) + describe: 'Indicates the alias to be used in the future to refer to the keystore.' + , type: 'string' + , default: 'mykey' + }) .option('ks-pass', { - describe: 'The password of the keystore.' - , type: 'string' - , default: 'openstf' - }) + describe: 'The password of the keystore.' + , type: 'string' + , default: 'openstf' + }) .option('ks-key-pass', { - describe: 'The password of the private key contained in keystore.' - , type: 'string' - , default: 'openstf' - }) + describe: 'The password of the private key contained in keystore.' + , type: 'string' + , default: 'openstf' + }) .option('ks-keyalg', { - describe: 'The algorithm that is used to generate the key.' - , type: 'string' - , default: 'RSA' - }) + describe: 'The algorithm that is used to generate the key.' + , type: 'string' + , default: 'RSA' + }) .option('ks-validity', { - describe: 'Number of days of keystore validity.' - , type: 'number' - , default: '90' - }) + describe: 'Number of days of keystore validity.' + , type: 'number' + , default: '90' + }) .option('ks-keysize', { - describe: 'Key size of the keystore.' - , type: 'number' - , default: '2048' - }) + describe: 'Key size of the keystore.' + , type: 'number' + , default: '2048' + }) .option('ks-dname', { - describe: 'Keystore Distinguished Name, contain Common Name(CN), ' + + describe: 'Keystore Distinguished Name, contain Common Name(CN), ' + 'Organizational Unit (OU), Oranization(O), Locality (L), State (S) and Country (C).' - , type: 'string' - , default: 'CN=openstf.io, OU=openstf, O=openstf, L=PaloAlto, S=California, C=US' - }) + , type: 'string' + , default: 'CN=openstf.io, OU=openstf, O=openstf, L=PaloAlto, S=California, C=US' + }) .option('secret', { - alias: 's' - , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + + alias: 's' + , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + 'knows this token can freely enter the system if they want, so keep ' + 'it safe.' - , type: 'string' - , default: process.env.SECRET - , demand: true - }) + , type: 'string' + , default: process.env.SECRET + , demand: true + }) .option('ssid', { - alias: 'i' - , describe: 'The name of the session ID cookie.' - , type: 'string' - , default: process.env.SSID || 'ssid' - }) + alias: 'i' + , describe: 'The name of the session ID cookie.' + , type: 'string' + , default: process.env.SSID || 'ssid' + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_STORAGE_TEMP_` (e.g. ' + diff --git a/lib/cli/triproxy/index.js b/lib/cli/triproxy/index.js index 6c4000595c..362e6a0d4c 100644 --- a/lib/cli/triproxy/index.js +++ b/lib/cli/triproxy/index.js @@ -7,28 +7,28 @@ export const builder = function(yargs) { .env('STF_TRIPROXY') .strict() .option('bind-dealer', { - alias: 'd' - , describe: 'The address to bind the ZeroMQ DEALER endpoint to.' - , type: 'string' - , default: 'tcp://*:7112' - }) + alias: 'd' + , describe: 'The address to bind the ZeroMQ DEALER endpoint to.' + , type: 'string' + , default: 'tcp://*:7112' + }) .option('bind-pub', { - alias: 'u' - , describe: 'The address to bind the ZeroMQ PUB endpoint to.' - , type: 'string' - , default: 'tcp://*:7111' - }) + alias: 'u' + , describe: 'The address to bind the ZeroMQ PUB endpoint to.' + , type: 'string' + , default: 'tcp://*:7111' + }) .option('bind-pull', { - alias: 'p' - , describe: 'The address to bind the ZeroMQ PULL endpoint to.' - , type: 'string' - , default: 'tcp://*:7113' - }) + alias: 'p' + , describe: 'The address to bind the ZeroMQ PULL endpoint to.' + , type: 'string' + , default: 'tcp://*:7113' + }) .option('name', { - describe: 'An easily identifiable name for log output.' - , type: 'string' - , default: os.hostname() - }) + describe: 'An easily identifiable name for log output.' + , type: 'string' + , default: os.hostname() + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_TRIPROXY_` (e.g. ' + diff --git a/lib/cli/vnc-device/example b/lib/cli/vnc-device/example index 9f54ca29cd..73a580af04 100644 --- a/lib/cli/vnc-device/example +++ b/lib/cli/vnc-device/example @@ -1,10 +1,10 @@ stf vnc-device \ - --serial vnc-device \ + --serial "346AEE38-F1C5-4FE2-BBCC-7B75786A44EE" \ --device-name "Generic VNC" \ --host localhost \ --screen-port 7409 \ --vnc-port 5900 \ - --provider local-provider \ + --provider di-smirnov \ --public-ip localhost \ --screen-ws-url-pattern "ws://localhost:7409" \ --storage-url http://localhost:7100/ \ diff --git a/lib/cli/vnc-device/index.js b/lib/cli/vnc-device/index.js index fa3e2ef106..75851484e7 100644 --- a/lib/cli/vnc-device/index.js +++ b/lib/cli/vnc-device/index.js @@ -5,170 +5,170 @@ export const describe = 'Start an vnc device provider.' export function builder(yargs) { - return yargs - .strict() - .option('connect-push', { - alias: 'p' - , describe: 'ZeroMQ PULL endpoint to connect to.' - , array: true - , demand: true - }) - .option('connect-sub', { - alias: 's' - , describe: 'ZeroMQ PUB endpoint to connect to.' - , array: true - , demand: true - }) - .option('connect-url-pattern', { - describe: 'The URL pattern to use for `adb connect`.' - , type: 'string' - , default: '${publicIp}:${publicPort}' - }) - .option('group-timeout', { - alias: 't' - , describe: 'Timeout in seconds for automatic release of inactive devices.' - , type: 'number' - , default: 900 - }) - .option('heartbeat-interval', { - describe: 'Send interval in milliseconds for heartbeat messages.' - , type: 'number' - , default: 10000 - }) - .option('lock-rotation', { - describe: 'Whether to lock rotation when devices are being used. ' + + return yargs + .strict() + .option('connect-push', { + alias: 'p' + , describe: 'ZeroMQ PULL endpoint to connect to.' + , array: true + , demand: true + }) + .option('connect-sub', { + alias: 's' + , describe: 'ZeroMQ PUB endpoint to connect to.' + , array: true + , demand: true + }) + .option('connect-url-pattern', { + describe: 'The URL pattern to use for `adb connect`.' + , type: 'string' + , default: '${publicIp}:${publicPort}' + }) + .option('group-timeout', { + alias: 't' + , describe: 'Timeout in seconds for automatic release of inactive devices.' + , type: 'number' + , default: 900 + }) + .option('heartbeat-interval', { + describe: 'Send interval in milliseconds for heartbeat messages.' + , type: 'number' + , default: 10000 + }) + .option('lock-rotation', { + describe: 'Whether to lock rotation when devices are being used. ' + 'Otherwise changing device orientation may not always work due to ' + 'sensitive sensors quickly or immediately reverting it back to the ' + 'physical orientation.' - , type: 'boolean' - }) - .option('provider', { - alias: 'n' - , describe: 'Name of the provider.' - , type: 'string' - , demand: true - }) - .option('public-ip', { - describe: 'The IP or hostname to use in URLs.' - , type: 'string' - , demand: true - }) - .option('screen-jpeg-quality', { - describe: 'The JPG quality to use for the screen.' - , type: 'number' - , default: process.env.SCREEN_JPEG_QUALITY || 80 - }) - .option('screen-ping-interval', { - describe: 'The interval at which to send ping messages to keep the ' + + , type: 'boolean' + }) + .option('provider', { + alias: 'n' + , describe: 'Name of the provider.' + , type: 'string' + , demand: true + }) + .option('public-ip', { + describe: 'The IP or hostname to use in URLs.' + , type: 'string' + , demand: true + }) + .option('screen-jpeg-quality', { + describe: 'The JPG quality to use for the screen.' + , type: 'number' + , default: process.env.SCREEN_JPEG_QUALITY || 80 + }) + .option('screen-ping-interval', { + describe: 'The interval at which to send ping messages to keep the ' + 'screen WebSocket alive.' - , type: 'number' - , default: 30000 - }) - .option('screen-port', { - describe: 'Port allocated to the screen WebSocket.' - , type: 'number' - , demand: true - }) - .option('screen-ws-url-pattern', { - describe: 'The URL pattern to use for the screen WebSocket.' - , type: 'string' - , default: 'ws://${publicIp}:${publicPort}' - }) - .option('serial', { - describe: 'The USB serial number of the device.' - , type: 'string' - , demand: true - }) - .option('storage-url', { - alias: 'r' - , describe: 'The URL to the storage unit.' - , type: 'string' - , demand: true - }) - .option('connect-app-dealer', { - describe: 'App-side ZeroMQ DEALER endpoint to connect to.' - , array: true - , demand: true - }) - .option('connect-dev-dealer', { - describe: 'Device-side ZeroMQ DEALER endpoint to connect to.' - , array: true - , demand: true - }) - .option('vnc-port', { - describe: 'The port where vnc is started.' - , type: 'number' - , default: 9001 - , demand: true - }) - .option('device-url', { - describe: 'url for device' - , type: 'string' - , default: '127.0.0.1' - , demand: true - }) - .option('device-name', { - describe: 'Device name' - , type: 'string' - , default: 'Generic VNC Device' - }) - .option('device-os', { - describe: 'Device os' - , type: 'string' - , default: 'VNC Device' - }) - .option('device-password', { - describe: 'device password' - , type: 'string' - , default: 'Password' - }) - .option('device-type', { - describe: 'device type' - , type: 'string' - , default: 'VNC' - }) - .option('host', { - describe: 'Provider hostname.' - , type: 'string' - , demand: true - , default: '127.0.0.1' - }) + , type: 'number' + , default: 30000 + }) + .option('screen-port', { + describe: 'Port allocated to the screen WebSocket.' + , type: 'number' + , demand: true + }) + .option('screen-ws-url-pattern', { + describe: 'The URL pattern to use for the screen WebSocket.' + , type: 'string' + , default: 'ws://${publicIp}:${publicPort}' + }) + .option('serial', { + describe: 'The USB serial number of the device.' + , type: 'string' + , demand: true + }) + .option('storage-url', { + alias: 'r' + , describe: 'The URL to the storage unit.' + , type: 'string' + , demand: true + }) + .option('connect-app-dealer', { + describe: 'App-side ZeroMQ DEALER endpoint to connect to.' + , array: true + , demand: true + }) + .option('connect-dev-dealer', { + describe: 'Device-side ZeroMQ DEALER endpoint to connect to.' + , array: true + , demand: true + }) + .option('vnc-port', { + describe: 'The port where vnc is started.' + , type: 'number' + , default: 9001 + , demand: true + }) + .option('device-url', { + describe: 'url for device' + , type: 'string' + , default: '127.0.0.1' + , demand: true + }) + .option('device-name', { + describe: 'Device name' + , type: 'string' + , default: 'Generic VNC Device' + }) + .option('device-os', { + describe: 'Device os' + , type: 'string' + , default: 'VNC Device' + }) + .option('device-password', { + describe: 'device password' + , type: 'string' + , default: 'Password' + }) + .option('device-type', { + describe: 'device type' + , type: 'string' + , default: 'VNC' + }) + .option('host', { + describe: 'Provider hostname.' + , type: 'string' + , demand: true + , default: '127.0.0.1' + }) } export function handler(argv) { - return vncDevice({ - serial: argv.serial - , provider: argv.provider - , publicIp: argv.publicIp - , endpoints: { - sub: argv.connectSub - , push: argv.connectPush - , appDealer: argv.connectAppDealer - , devDealer: argv.connectDevDealer - } - , groupTimeout: argv.groupTimeout * 1000 // change to ms - , storageUrl: argv.storageUrl - , screenJpegQuality: argv.screenJpegQuality - , screenPingInterval: argv.screenPingInterval - , screenPort: argv.screenPort - , screenWsUrlPattern: argv.screenWsUrlPattern - , connectUrlPattern: argv.connectUrlPattern - , mjpegPort: argv.vncPort - , deviceUrl: argv.deviceUrl - , deviceOs: argv.deviceOs - , devicePassword: argv.devicePassword - , deviceType: argv.deviceType - , heartbeatInterval: argv.heartbeatInterval - , lockRotation: argv.lockRotation - , cleanup: argv.cleanup - , screenReset: argv.screenReset - , deviceName: argv.deviceName - , host: argv.host - }) + return vncDevice({ + serial: argv.serial + , provider: argv.provider + , publicIp: argv.publicIp + , endpoints: { + sub: argv.connectSub + , push: argv.connectPush + , appDealer: argv.connectAppDealer + , devDealer: argv.connectDevDealer + } + , groupTimeout: argv.groupTimeout * 1000 // change to ms + , storageUrl: argv.storageUrl + , screenJpegQuality: argv.screenJpegQuality + , screenPingInterval: argv.screenPingInterval + , screenPort: argv.screenPort + , screenWsUrlPattern: argv.screenWsUrlPattern + , connectUrlPattern: argv.connectUrlPattern + , mjpegPort: argv.vncPort + , deviceUrl: argv.deviceUrl + , deviceOs: argv.deviceOs + , devicePassword: argv.devicePassword + , deviceType: argv.deviceType + , heartbeatInterval: argv.heartbeatInterval + , lockRotation: argv.lockRotation + , cleanup: argv.cleanup + , screenReset: argv.screenReset + , deviceName: argv.deviceName + , host: argv.host + }) } export default { - command - , describe - , builder - , handler + command + , describe + , builder + , handler } diff --git a/lib/cli/websocket/index.js b/lib/cli/websocket/index.js index 03f0c6c169..25a78ca7ea 100644 --- a/lib/cli/websocket/index.js +++ b/lib/cli/websocket/index.js @@ -6,44 +6,44 @@ export const builder = function(yargs) { .env('STF_WEBSOCKET') .strict() .option('connect-push', { - alias: 'c' - , describe: 'App-side ZeroMQ PULL endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'c' + , describe: 'App-side ZeroMQ PULL endpoint to connect to.' + , array: true + , demand: true + }) .option('connect-sub', { - alias: 'u' - , describe: 'App-side ZeroMQ PUB endpoint to connect to.' - , array: true - , demand: true - }) + alias: 'u' + , describe: 'App-side ZeroMQ PUB endpoint to connect to.' + , array: true + , demand: true + }) .option('port', { - alias: 'p' - , describe: 'The port to bind to.' - , type: 'number' - , default: process.env.PORT || 7110 - }) + alias: 'p' + , describe: 'The port to bind to.' + , type: 'number' + , default: process.env.PORT || 7110 + }) .option('secret', { - alias: 's' - , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + + alias: 's' + , describe: 'The secret to use for auth JSON Web Tokens. Anyone who ' + 'knows this token can freely enter the system if they want, so keep ' + 'it safe.' - , type: 'string' - , default: process.env.SECRET - , demand: true - }) + , type: 'string' + , default: process.env.SECRET + , demand: true + }) .option('ssid', { - alias: 'i' - , describe: 'The name of the session ID cookie.' - , type: 'string' - , default: process.env.SSID || 'ssid' - }) + alias: 'i' + , describe: 'The name of the session ID cookie.' + , type: 'string' + , default: process.env.SSID || 'ssid' + }) .option('storage-url', { - alias: 'r' - , describe: 'URL to the storage unit.' - , type: 'string' - , demand: true - }) + alias: 'r' + , describe: 'URL to the storage unit.' + , type: 'string' + , demand: true + }) .epilog('Each option can be be overwritten with an environment variable ' + 'by converting the option to uppercase, replacing dashes with ' + 'underscores and prefixing it with `STF_WEBSOCKET_` (e.g. ' + diff --git a/lib/db/api.js b/lib/db/api.js new file mode 100755 index 0000000000..5bd2d68106 --- /dev/null +++ b/lib/db/api.js @@ -0,0 +1,3371 @@ +/* eslint-disable valid-jsdoc */ +/** + * Copyright © 2024 contains code contributed by V Kontakte LLC, authors: Daniil Smirnov, Egor Platonov, Aleksey Chistov - Licensed under the Apache license 2.0 + **/ +import util from 'util' +import db from './index.js' +import wireutil from '../wire/util.js' +import uuid from 'uuid' +import * as apiutil from '../util/apiutil.js' +import * as Sentry from '@sentry/node' +import Promise from 'bluebird' +import _ from 'lodash' + +import logger from '../util/logger.js' +import {AssertionError} from 'assert' + +const log = logger.createLogger('dbapi') + +// const dbapi = Object.create(null) + +// constant default device data + +const DEFAULT_IOS_DEVICE_ARGS = { + DENSITY: 2 + , FPS: 60 + , ID: 0 + , ROTATION: 0 + , SECURE: true + , SIZE: 4.971253395080566 + , XDPI: 294.9670104980469 + , YDPI: 295.56298828125 +} + +// + +// dbapi.DuplicateSecondaryIndexError = function DuplicateSecondaryIndexError() { +export const DuplicateSecondaryIndexError = function DuplicateSecondaryIndexError() { + Error.call(this) + this.name = 'DuplicateSecondaryIndexError' + Error.captureStackTrace(this, DuplicateSecondaryIndexError) +} + +util.inherits(DuplicateSecondaryIndexError, Error) + + +const trace = (name, args, fn) => { + const addedAttributes = Object.fromEntries(Object.entries(args).map(([k, v]) => ( + ['dbapi.' + k, v] + ))) + return Sentry.startSpan({op: 'dbapi', name, attributes: addedAttributes}, fn) +} + +/** + * @deprecated Do not use locks in database. + */ +export const unlockBookingObjects = function() { + return trace('unlockBookingObjects', {}, () => { + return db.connect().then(client => { + return Promise.all([ + client.collection('users').updateMany( + {}, + { + $set: {'groups.lock': false} + } + ) + , client.collection('devices').updateMany( + {}, + { + $set: {'group.lock': false} + } + ) + , client.collection('groups').updateMany( + {}, + { + $set: { + 'lock.user': false + , 'lock.admin': false + } + } + ) + ]) + }) + }) +} + +// dbapi.getNow = function() { +export const getNow = function() { + return trace('getNow', {}, () => { + return new Date() + }) +} + + +// dbapi.createBootStrap = function(env) { +export const createBootStrap = function(env) { + return trace('createBootStrap', {env}, () => { + const now = Date.now() + + function updateUsersForMigration(group) { + return getUsers().then(function(users) { + return Promise.map(users, function(user) { + return db.connect().then(client => { + let data = { + privilege: user.email !== group.owner.email ? apiutil.USER : apiutil.ADMIN + , 'groups.subscribed': [] + , 'groups.lock': false + , 'groups.quotas.allocated.number': group.envUserGroupsNumber + , 'groups.quotas.allocated.duration': group.envUserGroupsDuration + , 'groups.quotas.consumed.duration': 0 + , 'groups.quotas.consumed.number': 0 + , 'groups.defaultGroupsNumber': user.email !== group.owner.email ? 0 : group.envUserGroupsNumber + , 'groups.defaultGroupsDuration': user.email !== group.owner.email ? 0 : group.envUserGroupsDuration + , 'groups.defaultGroupsRepetitions': user.email !== group.owner.email ? 0 : group.envUserGroupsRepetitions + , 'groups.repetitions': group.envUserGroupsRepetitions + } + client.collection('users').updateOne( + {email: user.email}, + { + $set: data + } + ) + .then(function(stats) { + if (stats.modifiedCount > 0) { + return addGroupUser(group.id, user.email) + } + return stats + }) + }) + }) + }) + } + + function getDevices() { + return db.connect().then(client => { + return client.collection('devices').find().toArray() + }) + } + + function updateDevicesForMigration(group) { + return getDevices().then(function(devices) { + return Promise.map(devices, function(device) { + log.info('Migrating device ' + device.serial) + return db.connect().then(client => { + let data = { + 'group.id': group.id + , 'group.name': group.name + , 'group.lifeTime': group.lifeTime + , 'group.owner': group.owner + , 'group.origin': group.origin + , 'group.class': group.class + , 'group.repetitions': group.repetitions + , 'group.originName': group.originName + , 'group.lock': false + } + return client.collection('devices').updateOne( + {serial: device.serial}, + { + $set: data + } + ).then(function(stats) { + if (stats.modifiedCount > 0) { + return addOriginGroupDevice(group, device.serial) + } + return stats + }) + }) + }) + }) + } + + return createGroup({ + name: env.STF_ROOT_GROUP_NAME + , owner: { + email: env.STF_ADMIN_EMAIL + , name: env.STF_ADMIN_NAME + } + , users: [env.STF_ADMIN_EMAIL] + , privilege: apiutil.ROOT + , class: apiutil.BOOKABLE + , repetitions: 0 + , duration: 0 + , isActive: true + , state: apiutil.READY + , dates: [{ + start: new Date(now) + , stop: new Date(now + apiutil.TEN_YEARS) + }] + , envUserGroupsNumber: apiutil.MAX_USER_GROUPS_NUMBER + , envUserGroupsDuration: apiutil.MAX_USER_GROUPS_DURATION + , envUserGroupsRepetitions: apiutil.MAX_USER_GROUPS_REPETITIONS + }) + .then(function(group) { + return saveUserAfterLogin({ + name: group.owner.name + , email: group.owner.email + , ip: '127.0.0.1' + }) + .then(function() { + return updateUsersForMigration(group) + }) + .then(function() { + return updateDevicesForMigration(group) + }) + .then(function() { + return reserveUserGroupInstance(group.owner.email) + }) + }) + }) +} + +// dbapi.deleteDevice = function(serial) { +export const deleteDevice = function(serial) { + return trace('deleteDevice', {serial}, () => { + return db.connect().then(client => { + return client.collection('devices').deleteOne({serial: serial}) + }) + }) +} + +export const deleteUser = function(email) { + return trace('deleteUser', {email}, () => { + return db.connect().then(client => { + return client.collection('users').deleteOne({email: email}) + }) + }) +} + +export const getReadyGroupsOrderByIndex = function(index) { + return trace('getReadyGroupsOrderByIndex', {index}, () => { + const options = { + // Sort matched documents in descending order by rating + sort: [index], + } + return db.connect().then(client => { + return client.collection('groups') + .find( + { + state: + { + $ne: apiutil.PENDING + } + } + , options + ) + .toArray() + }) + }) +} + +// dbapi.getGroupsByIndex = function(value, index) { +export const getGroupsByIndex = function(value, index) { + return trace('getGroupsByIndex', {value, index}, () => { + let findIndex = {} + findIndex[index] = value + return db.connect().then(client => { + return client.collection('groups').find(findIndex).toArray() + }) + }) +} + + +// dbapi.getGroupByIndex = function(value, index) { +export const getGroupByIndex = function(value, index) { + return trace('getGroupByIndex', {value, index}, () => { + return getGroupsByIndex(value, index) + .then(function(array) { + return array[0] + }) + }) +} + +// dbapi.getGroupsByUser = function(email) { +export const getGroupsByUser = function(email) { + return trace('getGroupsByUser', {email}, () => { + return db.connect().then(client => { + return client.collection('groups').find({users: {$in: [email]}}).toArray() + }) + }) +} + +// dbapi.getGroup = function(id) { +export const getGroup = function(id) { + return trace('getGroup', {id}, () => { + return db.connect().then(client => { + return client.collection('groups').findOne({id: id}) + }) + }) +} + +// dbapi.getGroups = function() { +export const getGroups = function() { + return trace('getGroups', {}, () => { + return db.connect().then(client => { + return client.collection('groups').find().toArray() + }) + }) +} + +// dbapi.getUsers = function() { +export const getUsers = function() { + return trace('getUsers', {}, () => { + return db.connect().then(client => { + return client.collection('users').find().toArray() + }) + }) +} + +// dbapi.getEmails = function() { +export const getEmails = function() { + return trace('getEmails', {}, () => { + return db.connect().then(client => { + return client.collection('users') + .find( + { + privilege: + { + $ne: apiutil.ADMIN + } + } + ) + .project({email: 1, _id: 0}) + .toArray() + }) + }) +} + +// dbapi.addGroupUser = function(id, email) { +export const addGroupUser = function(id, email) { + return trace('addGroupUser', {id, email}, () => { + return db.connect() + .then(client => { + return Promise.all([ + client.collection('groups').updateOne( + { + id: id + }, + { + $addToSet: { + users: email + } + } + ) + , client.collection('users').updateOne( + { + email: email + }, + { + $addToSet: { + 'groups.subscribed': id + } + } + ) + ]) + }) + .then(function(stats) { + return stats[0].modifiedCount === 0 && stats[1].modifiedCount === 0 ? 'unchanged' : 'added' + }) + }) +} + +// dbapi.getAdmins = function() { +export const getAdmins = function() { + return trace('getAdmins', {}, () => { + return db.connect().then(client => { + return client.collection('users') + .find({ + privilege: apiutil.ADMIN + }) + .project({email: 1, _id: 0}) + .toArray() + }) + }) +} + + +// dbapi.addAdminsToGroup = function(id) { +export const addAdminsToGroup = function(id) { + return trace('addAdminsToGroup', {id}, () => { + return getAdmins().then(admins => { + return db.connect().then(client => { + return client.collection('groups').findOne({id: id}).then(group => { + admins.forEach((admin) => { + let newUsers = group.users + if (!newUsers.includes(admin.email)) { + newUsers.push(admin.email) + } + return client.collection('groups').updateOne( + { + id: id + }, + { + $set: { + users: newUsers + } + } + ).then(() => { + return client.collection('users').findOne({email: admin.email}) + .then(user => { + let newSubs = user.groups.subscribed + newSubs.push(id) + return client.collection('users').updateOne( + {email: admin.email}, + { + $set: { + 'groups.subscribed': newSubs + } + } + ) + }) + }) + }) + }) + }) + }) + }) +} + +// dbapi.removeGroupUser = function(id, email) { +export const removeGroupUser = function(id, email) { + return trace('removeGroupUser', {id, email}, () => { + return db.connect().then(client => { + return Promise.all([ + client.collection('groups').updateOne( + {id: id} + , { + $pull: { + users: email + } + } + ) + , client.collection('users').updateOne( + {email: email} + , + { + $pull: {'groups.subscribed': id} + } + ) + ]) + }) + .then(function() { + return 'deleted' + }) + }) +} + +export const lockDeviceByCurrent = function(groups, serial) { + return trace('lockDeviceByCurrent', {groups, serial}, () => { + function wrappedlockDeviceByCurrent() { + return db.connect().then(client => { + return client.collection('devices').findOne({serial: serial}).then(oldDoc => { + return client.collection('devices').updateOne( + {serial: serial}, + [{ + $set: { + 'group.lock': { + $cond: [ + { + $and: [ + {$eq: ['$group.lock', false]} + , {$not: [{$eq: [{$setIntersection: [groups, ['$group.id']]}, []]}]} + ] + } + , true + , '$group.lock' + ] + } + } + }] + ).then(updateStats => { + return client.collection('devices').findOne({serial: serial}).then(newDoc => { + updateStats.changes = [ + {new_val: {...newDoc}, old_val: {...oldDoc}} + ] + return updateStats + }) + }) + }) + }) + .then(function(stats) { + return apiutil.lockDeviceResult(stats, loadDeviceByCurrent, groups, serial) + }) + } + + return apiutil.setIntervalWrapper( + wrappedlockDeviceByCurrent + , 10 + , Math.random() * 500 + 50) + }) +} + +// dbapi.lockDeviceByOrigin = function(groups, serial) { +export const lockDeviceByOrigin = function(groups, serial) { + return trace('lockDeviceByOrigin', {groups, serial}, () => { + function wrappedlockDeviceByOrigin() { + return db.connect().then(client => { + return client.collection('devices').findOne({serial: serial}).then(oldDoc => { + return client.collection('devices').updateOne( + {serial: serial}, + [{ + $set: { + 'group.lock': { + $cond: [ + { + $and: [ + {$eq: ['$group.lock', false]} + , {$not: [{$eq: [{$setIntersection: [groups, ['$group.origin']]}, []]}]} + ] + } + , true + , '$group.lock' + ] + } + } + }] + ).then(updateStats => { + return client.collection('devices').findOne({serial: serial}).then(newDoc => { + updateStats.changes = [ + {new_val: {...newDoc}, old_val: {...oldDoc}} + ] + return updateStats + }) + }) + }) + }) + .then(function(stats) { + return apiutil.lockDeviceResult(stats, loadDeviceByOrigin, groups, serial) + }) + } + + return apiutil.setIntervalWrapper( + wrappedlockDeviceByOrigin + , 10 + , Math.random() * 500 + 50) + }) +} + +// dbapi.addOriginGroupDevice = function(group, serial) { +export const addOriginGroupDevice = function(group, serial) { + return trace('addOriginGroupDevice', {group, serial}, () => { + return db.connect().then(client => { + return client.collection('groups').findOne({id: group.id}).then((groupData) => { + let devices = groupData.devices + if (!devices.includes(serial)) { + devices.push(serial) + } + else { + return getGroup(group.id) + } + return client.collection('groups').updateOne( + { + id: group.id + }, + { + $set: { + devices: devices + } + }).then(function() { + return getGroup(group.id) + }) + }) + }) + }) +} + +// dbapi.removeOriginGroupDevice = function(group, serial) { +export const removeOriginGroupDevice = function(group, serial) { + return trace('removeOriginGroupDevice', {group, serial}, () => { + return db.connect().then(client => { + return client.collection('groups').updateOne( + {id: group.id} + , [ + {$set: {devices: {$setDifference: ['$devices', [serial]]}}} + ] + ) + .then(function() { + return getGroup(group.id) + }) + }) + }) +} + +// dbapi.addGroupDevices = function(group, serials) { +export const addGroupDevices = function(group, serials) { + return trace('addGroupDevices', {group, serials}, () => { + const duration = apiutil.computeDuration(group, serials.length) + + return updateUserGroupDuration(group.owner.email, group.duration, duration) + .then(function(stats) { + if (stats.modifiedCount > 0) { + return updateGroup( + group.id + , { + duration: duration + , devices: _.union(group.devices, serials) + }) + .then((data) => { + if (group.class === apiutil.ONCE) { + return updateDevicesCurrentGroup(serials, group) + .then(() => data) + } + return data + }) + } + return Promise.reject('quota is reached') + }) + }) +} + +// dbapi.removeGroupDevices = function(group, serials) { +export const removeGroupDevices = function(group, serials) { + return trace('removeGroupDevices', {group, serials}, () => { + const duration = apiutil.computeDuration(group, -serials.length) + + return updateUserGroupDuration(group.owner.email, group.duration, duration) + .then(function() { + return updateGroup( + group.id + , { + duration: duration + , devices: _.difference(group.devices, serials) + }) + }) + }) +} + +/** + * @deprecated Do not use locks in database. + */ +function setLockOnDevice(serial, state) { + return db.connect().then(client => { + return client.collection('devices').findOne({serial: serial}).then(device => { + return client.collection('devices').updateOne({ + serial: serial + } + , + { + $set: {'group.lock': device.group.lock !== state ? state : device.group.lock} + } + ) + }) + }) +} + +/** + * @deprecated Do not use locks in database. + */ +export const lockDevice = function(serial) { + return trace('lockDevice', {serial}, () => { + return setLockOnDevice(serial, true) + }) +} + +/** + * @deprecated Do not use locks in database. + */ +export const lockDevices = function(serials) { + return trace('lockDevices', {serials}, () => { + return setLockOnDevices(serials, true) + }) +} + +// dbapi.unlockDevice = function(serial) { +export const unlockDevice = function(serial) { + return trace('unlockDevice', {serial}, () => { + return setLockOnDevice(serial, false) + }) +} + +// dbapi.unlockDevices = function(serials) { +export const unlockDevices = function(serials) { + return trace('unlockDevices', {serials}, () => { + return setLockOnDevices(serials, false) + }) +} + +/** + * @deprecated Do not use locks in database. + */ +export const setLockOnDevices = function(serials, lock) { + return trace('setLockOnDevices', {serials, lock}, () => { + return db.connect().then(client => { + return client.collection('devices').updateMany( + {serial: {$in: serials}} + , { + $set: { + 'group.lock': lock + } + } + ) + }) + }) +} + +/** + * @deprecated Do not use locks in database. + */ +function setLockOnUser(email, state) { + return db.connect().then(client => { + return client.collection('users').findOne({email: email}).then(oldDoc => { + return client.collection('users').updateOne({ + email: email + } + , + { + $set: {'groups.lock': oldDoc.groups.lock !== state ? state : oldDoc.groups.lock} + } + ) + .then(updateStats => { + return client.collection('users').findOne({email: email}).then(newDoc => { + updateStats.changes = [ + {new_val: {...newDoc}, old_val: {...oldDoc}} + ] + return updateStats + }) + }) + }) + }) +} + +// dbapi.lockUser = function(email) { +export const lockUser = function(email) { + return trace('lockUser', {email}, () => { + function wrappedlockUser() { + return setLockOnUser(email, true) + .then(function(stats) { + return apiutil.lockResult(stats) + }) + } + + return apiutil.setIntervalWrapper( + wrappedlockUser + , 10 + , Math.random() * 500 + 50) + }) +} + +// dbapi.unlockUser = function(email) { +export const unlockUser = function(email) { + return trace('unlockUser', {email}, () => { + return setLockOnUser(email, false) + }) +} + +/** + * @deprecated Do not use locks in database. + */ +export const lockGroupByOwner = function(email, id) { + return trace('lockGroupByOwner', {email, id}, () => { + async function wrappedlockGroupByOwner() { + return db.connect().then(async client => { + const rootGroup = await getRootGroup() + const triggeredUser = await client.collection('users').findOne({email: email}) + const group = await client.collection('groups').findOne({id: id}) + if (!group) { + return { + modifiedCount: 0 + , matchedCount: 0 + } + } + if (!(group.owner.email === email || rootGroup.owner.email === email || triggeredUser.privilege === apiutil.ADMIN)) { + return {modifiedCount: 0, matchedCount: 1, changes: [ + {new_val: {...group}, old_val: {...group}} + ]} + } + const updateStats = await client.collection('groups').updateOne({ + id: id + , 'lock.user': false + , 'lock.admin': false + }, + { + $set: { + 'lock.user': true + } + }) + const newDoc = await client.collection('groups').findOne({id: id}) + updateStats.changes = [ + {new_val: {...newDoc}, old_val: {...group}} + ] + return updateStats + }) + .then(function(stats) { + const result = apiutil.lockResult(stats) + + if (!result.status) { + return getGroupAsOwnerOrAdmin(email, id).then(function(group) { + if (!group) { // If group doen't exist or it's not our + result.data.locked = false + result.status = true + } + log.info(`lockGroupByOwner ${email}, ${id} results: ${result}`) + return result + }) + } + log.info(`lockGroupByOwner ${email}, ${id} results 2: ${result}`) + return result + }) + } + + return apiutil.setIntervalWrapper( + wrappedlockGroupByOwner + , 10 + , Math.random() * 500 + 50) + }) +} + +/** + * @deprecated Do not use locks in database. + */ +export const lockGroup = function(id) { + return trace('lockGroup', {id}, () => { + function wrappedlockGroup() { + return db.connect().then(client => { + return client.collection('groups').updateOne( + { + id: id + , 'lock.user': false + , 'lock.admin': false + }, + { + $set: { + 'lock.user': true + } + } + ).then(function(stats) { + return apiutil.lockResult(stats) + }) + }) + } + + return apiutil.setIntervalWrapper( + wrappedlockGroup + , 10 + , Math.random() * 500 + 50) + }) +} + +/** + * @deprecated Do not use locks in database. + */ +export const unlockGroup = function(id) { + return trace('unlockGroup', {id}, () => { + return db.connect().then(client => { + return client.collection('groups').updateMany( + {id: id}, + { + $set: { + 'lock.user': false + } + } + ) + }) + }) +} + +/** + * @deprecated Do not use locks in database. + */ +export const adminLockGroup = function(id, lock) { + return trace('adminLockGroup', {id, lock}, () => { + function wrappedAdminLockGroup() { + return db.connect().then(client => { + return client.collection('groups').findOneAndUpdate({ + id: id + , 'lock.user': false + , 'lock.admin': false + }, + { + $set: { + 'lock.user': false + , 'lock.admin': true + } + }, + { + returnDocument: 'after' + }).then(function(group) { + if (group === null) { + return {status: false, data: false} + } + lock.group = group + return {status: true, data: true} + }) + }) + } + + return apiutil.setIntervalWrapper( + wrappedAdminLockGroup + , 10 + , Math.random() * 500 + 50) + }) +} + +/** + * @deprecated Do not use locks in database. + */ +export const adminUnlockGroup = function(lock) { + return trace('adminUnlockGroup', {lock}, () => { + if (lock.group) { + return db.connect().then(client => { + return client.collection('groups').updateOne( + { + id: lock.group.id + } + , { + $set: { + 'lock.user': false + , 'lock.admin': false + } + } + ) + }) + } + return true + }) +} + +// dbapi.getRootGroup = function() { +export const getRootGroup = function() { + return trace('getRootGroup', {}, () => { + return getGroupByIndex(apiutil.ROOT, 'privilege').then(function(group) { + if (!group) { + throw new Error('Root group not found') + } + return group + }) + }) +} + +// dbapi.getUserGroup = function(email, id) { +export const getUserGroup = function(email, id) { + return trace('getUserGroup', {email, id}, () => { + return db.connect().then(client => { + return client.collection('groups').find({ + users: {$in: [email]} + , id: id + }).toArray().then(groups => { + return groups[0] + }) + }) + }) +} + +// dbapi.getUserGroups = function(email) { +export const getUserGroups = function(email) { + return trace('getUserGroups', {email}, () => { + return db.connect().then(client => { + return client.collection('groups').find({users: {$in: [email]}}).toArray() + }) + }) +} + +// dbapi.getOnlyUserGroups = function(email) { +export const getOnlyUserGroups = function(email) { + return trace('getOnlyUserGroups', {email}, () => { + return db.connect().then(client => { + return client.collection('groups').find({ + users: {$in: [email]} + , 'owner.email': {$ne: email} + }).toArray() + }) + }) +} + +// dbapi.getTransientGroups = function() { +export const getTransientGroups = function() { + return trace('getTransientGroups', {}, () => { + return db.connect().then(client => { + return client.collection('groups').find({ + class: {$nin: [apiutil.BOOKABLE, apiutil.STANDARD]} + } + ).toArray() + }) + }) +} + +// dbapi.getDeviceTransientGroups = function(serial) { +export const getDeviceTransientGroups = function(serial) { + return trace('getDeviceTransientGroups', {serial}, () => { + return db.connect().then(client => { + return client.collection('groups').find({ + devices: serial + , class: {$nin: [apiutil.BOOKABLE, apiutil.STANDARD]} + } + ).toArray() + }) + }) +} + +// dbapi.isDeviceBooked = function(serial) { +export const isDeviceBooked = function(serial) { + return trace('isDeviceBooked', {serial}, () => { + return getDeviceTransientGroups(serial) + .then(function(groups) { + return groups.length > 0 + }) + }) +} + +// dbapi.isRemoveGroupUserAllowed = function(email, targetGroup) { +export const isRemoveGroupUserAllowed = function(email, targetGroup) { + return trace('isRemoveGroupUserAllowed', {email, targetGroup}, () => { + if (targetGroup.class !== apiutil.BOOKABLE) { + return Promise.resolve(true) + } + return db.connect().then(client => { + return client.collection('groups').aggregate([ + {$match: {'owner.email': email}} + , { + $match: { + $and: [ + {$ne: ['$class', apiutil.BOOKABLE]} + , {$ne: ['$class', apiutil.STANDARD]} + , {$not: [{$eq: [{$setIntersection: [targetGroup.devices, ['$devices']]}, []]}]} + ] + } + } + ]).toArray() + }) + .then(function(groups) { + return groups.length === 0 + }) + }) +} + +// dbapi.isUpdateDeviceOriginGroupAllowed = function(serial, targetGroup) { +export const isUpdateDeviceOriginGroupAllowed = function(serial, targetGroup) { + return trace('isUpdateDeviceOriginGroupAllowed', {serial, targetGroup}, () => { + return getDeviceTransientGroups(serial) + .then(function(groups) { + if (groups.length) { + if (targetGroup.class === apiutil.STANDARD) { + return false + } + for (const group of groups) { + if (targetGroup.users.indexOf(group.owner.email) < 0) { + return false + } + } + } + return true + }) + }) +} + +// dbapi.getDeviceGroups = function(serial) { +export const getDeviceGroups = function(serial) { + return trace('getDeviceGroups', {serial}, () => { + return db.connect().then(client => { + return client.collection('groups').find({ + devices: {$in: [serial]} + } + ).toArray() + }) + }) +} + +// dbapi.getGroupAsOwnerOrAdmin = function(email, id) { +export const getGroupAsOwnerOrAdmin = function(email, id) { + return trace('getGroupAsOwnerOrAdmin', {email, id}, () => { + return getGroup(id).then(function(group) { + if (group) { + if (email === group.owner.email) { + return group + } + return loadUser(email).then(function(user) { + if (user && user.privilege === apiutil.ADMIN) { + return group + } + return false + }) + } + return false + }) + }) +} + +// dbapi.getOwnerGroups = function(email) { +export const getOwnerGroups = function(email) { + return trace('getOwnerGroups', {email}, () => { + return getRootGroup().then(function(group) { + if (email === group.owner.email) { + return getGroups() + } + return getGroupsByIndex(email, 'owner') + }) + }) +} + +// dbapi.createGroup = function(data) { +export const createGroup = function(data) { + return trace('createGroup', {data}, () => { + const id = util.format('%s', uuid.v4()).replace(/-/g, '') + return db.connect().then(client => { + let object = Object.assign(data, { + id: id + , users: _.union(data.users, [data.owner.email]) + , devices: [] + , createdAt: getNow() + , lock: { + user: false + , admin: false + } + , ticket: null + , runUrl: data.runUrl + }) + return client.collection('groups').insertOne(object) + .then(() => { + return getGroup(id) + }) + }) + }) +} + +// dbapi.createUserGroup = function(data) { +export const createUserGroup = function(data) { + return trace('createUserGroup', {data}, () => { + return reserveUserGroupInstance(data.owner.email).then(function(stats) { + if (stats.modifiedCount > 0) { + return getRootGroup().then(function(rootGroup) { + data.users = [rootGroup.owner.email] + return createGroup(data).then(function(group) { + return Promise.all([ + addGroupUser(group.id, group.owner.email) + , addGroupUser(group.id, rootGroup.owner.email) + ]) + .then(function() { + return getGroup(group.id) + }) + }) + }) + } + else { + log.info(`Could not reserve group for user ${data.owner.email}`) + return false + } + }) + }) +} + +// dbapi.updateGroup = function(id, data) { +export const updateGroup = function(id, data) { + return trace('updateGroup', {id, data}, () => { + return db.connect().then(client => { + return client.collection('groups').updateOne( + {id: id} + , { + $set: data + } + ).then(() => { + return client.collection('groups').findOne({id: id}) + }) + }) + }) +} + +// dbapi.reserveUserGroupInstance = function(email) { +export const reserveUserGroupInstance = function(email) { + return trace('reserveUserGroupInstance', {email}, () => { + return db.connect().then(async client => { + const result = await client.collection('users').updateMany( + { + email + } + , [{ + $set: {'groups.quotas.consumed.number': { + $min: [{ + $sum: ['$groups.quotas.consumed.number', 1] + }, '$groups.quotas.allocated.number']} + } + }] + ) + return result + }) + }) +} + +// dbapi.releaseUserGroupInstance = function(email) { +export const releaseUserGroupInstance = function(email) { + return trace('releaseUserGroupInstance', {email}, () => { + return db.connect().then(async client => { + const result = await client.collection('users').updateMany( + { + email + } + , [{ + $set: {'groups.quotas.consumed.number': { + $max: [{ + $sum: ['$groups.quotas.consumed.number', -1] + }, 0]} + } + }] + ) + return result + }) + }) +} + +// dbapi.updateUserGroupDuration = function(email, oldDuration, newDuration) { +export const updateUserGroupDuration = function(email, oldDuration, newDuration) { + return trace('updateUserGroupDuration', {email, oldDuration, newDuration}, () => { + return db.connect().then(client => { + return client.collection('users').updateOne( + {email: email} + , [{ + $set: { + 'groups.quotas.consumed.duration': { + $cond: [ + {$lte: [{$sum: ['$groups.quotas.consumed.duration', newDuration, -oldDuration]}, '$groups.quotas.allocated.duration']} + , {$sum: ['$groups.quotas.consumed.duration', newDuration, -oldDuration]} + , '$groups.quotas.consumed.duration' + ] + } + } + }] + ) + }) + }) +} + +// dbapi.updateUserGroupsQuotas = function(email, duration, number, repetitions) { +export const updateUserGroupsQuotas = function(email, duration, number, repetitions) { + return trace('updateUserGroupsQuotas', {email, duration, number, repetitions}, () => { + return db.connect().then(client => { + return client.collection('users').findOne({email: email}).then(oldDoc => { + let consumed = oldDoc.groups.quotas.consumed.duration + let allocated = oldDoc.groups.quotas.allocated.duration + let consumedNumber = oldDoc.groups.quotas.consumed.number + let allocatedNumber = oldDoc.groups.quotas.allocated.number + return client.collection('users').updateOne( + {email: email} + , { + $set: { + 'groups.quotas.allocated.duration': duration && consumed <= duration && + (!number || consumedNumber <= number) ? duration : allocated + , 'groups.quotas.allocated.number': number && consumedNumber <= number && + (!duration || consumed <= duration) ? number : allocatedNumber + , 'groups.quotas.repetitions': repetitions || oldDoc.groups.quotas.repetitions + } + } + ) + .then(updateStats => { + return client.collection('users').findOne({email: email}).then(newDoc => { + updateStats.changes = [ + {new_val: {...newDoc}, old_val: {...oldDoc}} + ] + return updateStats + }) + }) + }) + }) + }) +} + +// dbapi.updateDefaultUserGroupsQuotas = function(email, duration, number, repetitions) { +export const updateDefaultUserGroupsQuotas = function(email, duration, number, repetitions) { + return trace('updateDefaultUserGroupsQuotas', {email, duration, number, repetitions}, () => { + return db.connect().then(client => { + return client.collection('users').updateOne( + {email: email} + , [{ + $set: { + defaultGroupsDuration: { + $cond: [ + { + $ne: [duration, null] + } + , duration + , '$groups.quotas.defaultGroupsDuration' + ] + } + , defaultGroupsNumber: { + $cond: [ + { + $ne: [number, null] + } + , number + , '$groups.quotas.defaultGroupsNumber' + ] + } + , defaultGroupsRepetitions: { + $cond: [ + { + $ne: [repetitions, null] + } + , repetitions + , '$groups.quotas.defaultGroupsRepetitions' + ] + } + } + }] + ) + }) + }) +} + +// dbapi.updateDeviceGroupName = function(serial, group) { +export const updateDeviceGroupName = function(serial, group) { + return trace('updateDeviceGroupName', {serial, group}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial} + , [{ + $set: { + 'group.name': { + $cond: [ + { + $eq: [apiutil.isOriginGroup(group.class), false] + } + , { + $cond: [ + { + $eq: [group.isActive, true] + } + , group.name + , '$group.name' + ] + } + , { + $cond: [ + { + $eq: ['$group.origin', '$group.id'] + } + , group.name + , '$group.name' + ] + } + ] + } + , 'group.originName': { + $cond: [ + { + $eq: [apiutil.isOriginGroup(group.class), true] + } + , group.name + , '$group.originName' + ] + } + } + }] + ) + }) + }) +} + +// dbapi.updateDeviceCurrentGroupFromOrigin = function(serial) { +export const updateDeviceCurrentGroupFromOrigin = function(serial) { + return trace('updateDeviceCurrentGroupFromOrigin', {serial}, () => { + return db.connect().then(client => { + return client.collection('devices').findOne({serial: serial}).then(device => { + return client.collection('groups').findOne({id: device.group.origin}).then(group => { + return client.collection('devices').updateOne( + {serial: serial} + , { + $set: { + 'group.id': device.group.origin + , 'group.name': device.group.originName + , 'group.owner': group.owner + , 'group.lifeTime': group.dates[0] + , 'group.class': group.class + , 'group.repetitions': group.repetitions + , 'group.runUrl': group.runUrl + } + } + ) + }) + }) + }) + }) +} + +// dbapi.askUpdateDeviceOriginGroup = function(serial, group, signature) { +export const askUpdateDeviceOriginGroup = function(serial, group, signature) { + return trace('askUpdateDeviceOriginGroup', {serial, group, signature}, () => { + return db.connect().then(client => { + return client.collection('groups').updateOne( + { + id: group.id + } + , { + $set: { + ticket: { + serial: serial + , signature: signature + } + } + } + ) + }) + }) +} + +// dbapi.updateDeviceOriginGroup = function(serial, group) { +export const updateDeviceOriginGroup = function(serial, group) { + return trace('updateDeviceOriginGroup', {serial, group}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial} + , [{ + $set: { + 'group.origin': group.id + , 'group.originName': group.name + , 'group.id': { + $cond: [ + { + $eq: ['$group.id', '$group.origin'] + } + , group.id + , '$group.id' + ] + } + , 'group.name': { + $cond: [ + { + $eq: ['$group.id', '$group.origin'] + } + , group.name + , '$group.name' + ] + } + , 'group.owner': { + $cond: [ + { + $eq: ['$group.id', '$group.origin'] + } + , group.owner + , '$group.owner' + ] + } + , 'group.lifeTime': { + $cond: [ + { + $eq: ['$group.id', '$group.origin'] + } + , group.dates[0] + , '$group.lifeTime' + ] + } + , 'group.class': { + $cond: [ + { + $eq: ['$group.id', '$group.origin'] + } + , group.class + , '$group.class' + ] + } + , 'group.repetitions': { + $cond: [ + { + $eq: ['$group.id', '$group.origin'] + } + , group.repetitions + , '$group.repetitions' + ] + } + } + }] + ) + }) + .then(function() { + return db.connect().then(clients => { + return clients.collection('devices').findOne({serial: serial}) + }) + }) + }) +} + +// dbapi.updateDeviceCurrentGroup = function(serial, group) { +export const updateDeviceCurrentGroup = function(serial, group) { + return trace('updateDeviceCurrentGroup', {serial, group}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: { + 'group.id': group.id + , 'group.name': group.name + , 'group.owner': group.owner + , 'group.lifeTime': group.dates[0] + , 'group.class': group.class + , 'group.repetitions': group.repetitions + } + } + ) + }) + }) +} + +// dbapi.updateDevicesCurrentGroup = function(serials, group) { +export const updateDevicesCurrentGroup = function(serials, group) { + return trace('updateDevicesCurrentGroup', {serials, group}, () => { + return db.connect().then(client => { + return client.collection('devices').updateMany( + {serial: {$in: serials}}, + { + $set: { + 'group.id': group.id + , 'group.name': group.name + , 'group.owner': group.owner + , 'group.lifeTime': group.dates[0] + , 'group.class': group.class + , 'group.repetitions': group.repetitions + , 'group.runUrl': group.runUrl + } + } + ) + }) + }) +} + +// dbapi.returnDeviceToOriginGroup = function(serial) { +export const returnDeviceToOriginGroup = function(serial) { + return trace('returnDeviceToOriginGroup', {serial}, () => { + return loadDeviceBySerial(serial) + .then((device) => { + return getRootGroup() + .then((group) => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: device.serial}, + { + $set: { + 'group.id': group.id + , 'group.name': group.name + , 'group.owner': group.owner + , 'group.lifeTime': group.dates[0] + , 'group.class': group.class + , 'group.repetitions': group.repetitions + } + } + ) + }) + }) + }) + }) +} + +// dbapi.returnDevicesToOriginGroup = function(serials) { +export const returnDevicesToOriginGroup = function(serials) { + return trace('returnDevicesToOriginGroup', {serials}, () => { + return getRootGroup() + .then(group => { + return db.connect().then(client => { + return client.collection('devices').updateMany( + {serial: {$in: serials}} + , { + $set: { + 'group.id': group.id + , 'group.name': group.name + , 'group.owner': group.owner + , 'group.lifeTime': group.dates[0] + , 'group.class': group.class + , 'group.repetitions': group.repetitions + } + } + ) + }) + }) + }) +} + +// dbapi.updateUserGroup = function(group, data) { +export const updateUserGroup = function(group, data) { + return trace('updateUserGroup', {group, data}, () => { + return updateUserGroupDuration(group.owner.email, group.duration, data.duration) + .then(function(stats) { + if (stats.modifiedCount > 0 || stats.matchedCount > 0 && group.duration === data.duration) { + return updateGroup(group.id, data) + } + return false + }) + }) +} + +// dbapi.deleteGroup = function(id) { +export const deleteGroup = function(id) { + return trace('deleteGroup', {id}, () => { + return db.connect().then(client => { + return client.collection('groups').deleteOne({id: id}) + }) + }) +} + +// dbapi.deleteUserGroup = function(id) { +export const deleteUserGroup = function(id) { + return trace('deleteUserGroup', {id}, () => { + function deleteUserGroup(group) { + return deleteGroup(group.id) + .then(() => { + return Promise.map(group.users, function(email) { + return removeGroupUser(group.id, email) + }) + }) + .then(() => { + return releaseUserGroupInstance(group.owner.email) + }) + .then(() => { + return updateUserGroupDuration(group.owner.email, group.duration, 0) + }) + .then(() => { + return returnDevicesToOriginGroup(group.devices) + }) + .then(function() { + return 'deleted' + }) + } + + return getGroup(id).then(function(group) { + if (group.privilege !== apiutil.ROOT) { + return deleteUserGroup(group) + } + return 'forbidden' + }) + }) +} + +// dbapi.createUser = function(email, name, ip) { +export const createUser = function(email, name, ip) { + return trace('createUser', {email, name, ip}, () => { + return getRootGroup().then(function(group) { + return loadUser(group.owner.email).then(function(adminUser) { + let userObj = { + email: email + , name: name + , ip: ip + , group: wireutil.makePrivateChannel() + , lastLoggedInAt: getNow() + , createdAt: getNow() + , forwards: [] + , settings: {} + , acceptedPolicy: false + , privilege: adminUser ? apiutil.USER : apiutil.ADMIN + , groups: { + subscribed: [] + , lock: false + , quotas: { + allocated: { + number: group.envUserGroupsNumber + , duration: group.envUserGroupsDuration + } + , consumed: { + number: 0 + , duration: 0 + } + , defaultGroupsNumber: group.envUserGroupsNumber + , defaultGroupsDuration: group.envUserGroupsDuration + , defaultGroupsRepetitions: group.envUserGroupsRepetitions + , repetitions: group.envUserGroupsRepetitions + } + } + } + return db.connect().then(client => { + return client.collection('users').insertOne(userObj) + }) + .then(function(stats) { + if (stats.insertedId) { + return addGroupUser(group.id, email).then(function() { + return loadUser(email).then(function(user) { + stats.changes = [ + {new_val: {...user}} + ] + return stats + }) + }) + } + return stats + }) + }) + }) + }) +} + +// dbapi.saveUserAfterLogin = function(user) { +export const saveUserAfterLogin = function(user) { + return trace('saveUserAfterLogin', {user}, () => { + return db.connect().then(client => { + return client.collection('users').updateOne({email: user.email}, + { + $set: { + name: user.name + , ip: user.ip + , lastLoggedInAt: getNow() + } + }) + .then(function(stats) { + if (stats.modifiedCount === 0) { + return createUser(user.email, user.name, user.ip) + } + return stats + }) + }) + }) +} + +// dbapi.loadUser = function(email) { +export const loadUser = function(email) { + return trace('loadUser', {email}, () => { + return db.connect().then(client => { + return client.collection('users').findOne({email: email}) + }) + }) +} + +// dbapi.updateUsersAlertMessage = function(alertMessage) { +export const updateUsersAlertMessage = function(alertMessage) { + return trace('updateUsersAlertMessage', {alertMessage}, () => { + return db.connect().then(client => { + return client.collection('users').updateOne( + { + email: apiutil.STF_ADMIN_EMAIL + } + , { + $set: Object.fromEntries(Object.entries(alertMessage).map(([key, value]) => + ['settings.alertMessage.' + key, value] + )), + } + ).then(updateStats => { + return client.collection('users').findOne({email: apiutil.STF_ADMIN_EMAIL}).then(updatedMainAdmin => { + updateStats.changes = [ + {new_val: {...updatedMainAdmin}} + ] + return updateStats + }) + }) + }) + }) +} + +// dbapi.updateUserSettings = function(email, changes) { +export const updateUserSettings = function(email, changes) { + return trace('updateUserSettings', {email, changes}, () => { + return db.connect().then(client => { + return client.collection('users').findOne({email: email}).then(user => { + return client.collection('users').updateOne( + { + email: email + } + , { + $set: { + settings: {...user.settings, ...changes} + } + } + ) + }) + }) + }) +} + +// dbapi.resetUserSettings = function(email) { +export const resetUserSettings = function(email) { + return trace('resetUserSettings', {email}, () => { + return db.connect().then(client => { + return client.collection('users').updateOne({email: email}, + { + $set: { + settings: {} + } + }) + }) + }) +} + +// dbapi.insertUserAdbKey = function(email, key) { +export const insertUserAdbKey = function(email, key) { + return trace('insertUserAdbKey', {email, key}, () => { + let data = { + title: key.title + , fingerprint: key.fingerprint + } + return db.connect().then(client => { + return client.collection('users').findOne({email: email}).then(user => { + let adbKeys = user.adbKeys ? user.adbKeys : [] + adbKeys.push(data) + return client.collection('users').updateOne( + {email: email} + , {$set: {adbKeys: user.adbKeys ? adbKeys : [data]}} + ) + }) + }) + }) +} + +// dbapi.deleteUserAdbKey = function(email, fingerprint) { +export const deleteUserAdbKey = function(email, fingerprint) { + return trace('deleteUserAdbKey', {email, fingerprint}, () => { + return db.connect().then(client => { + return client.collection('users').findOne({email: email}).then(user => { + return client.collection('users').updateOne( + {email: email} + , { + $set: { + adbKeys: user.adbKeys ? user.adbKeys.filter(key => { + return key.fingerprint !== fingerprint + }) : [] + } + } + ) + }) + }) + }) +} + +// dbapi.lookupUsersByAdbKey = function(fingerprint) { +export const lookupUsersByAdbKey = function(fingerprint) { + return trace('lookupUsersByAdbKey', {fingerprint}, () => { + return db.connect().then(client => { + return client.collection('users').find({ + adbKeys: fingerprint + }).toArray() + }) + }) +} + +// dbapi.lookupUserByAdbFingerprint = function(fingerprint) { +export const lookupUserByAdbFingerprint = function(fingerprint) { + return trace('lookupUserByAdbFingerprint', {fingerprint}, () => { + return db.connect().then(client => { + return client.collection('users').find( + {adbKeys: {$elemMatch: {fingerprint: fingerprint}}} + , {email: 1, name: 1, group: 1, _id: 0} + ).toArray() + .then(function(users) { + switch (users.length) { + case 1: + return users[0] + case 0: + return null + default: + throw new Error('Found multiple users for same ADB fingerprint') + } + }) + }) + }) +} + +// dbapi.lookupUserByVncAuthResponse = function(response, serial) { +export const lookupUserByVncAuthResponse = function(response, serial) { + return trace('lookupUserByVncAuthResponse', {response, serial}, () => { + return db.connect().then(client => { + return client.collection('vncauth').aggregate([ + { + $match: { + 'responsePerDevice.response': response + , 'responsePerDevice.serial': serial + } + } + , { + $lookup: { + from: 'users' + , localField: 'userId' + , foreignField: '_id' + , as: 'users' + } + } + , { + $project: { + email: 1 + , name: 1 + , group: 1 + } + } + ]).toArray() + }) + .then(function(groups) { + switch (groups.length) { + case 1: + return groups[0] + case 0: + return null + default: + throw new Error('Found multiple users with the same VNC response') + } + }) + }) +} + +// dbapi.loadUserDevices = function(email) { +export const loadUserDevices = function(email) { + return trace('loadUserDevices', {email}, () => { + return db.connect().then(client => { + return client.collection('users').findOne({email: email}).then(user => { + let userGroups = user.groups.subscribed + return client.collection('devices').find( + { + 'owner.email': email + , present: true + , 'group.id': {$in: userGroups} + } + ).toArray() + }) + }) + }) +} + +// dbapi.saveDeviceLog = function(serial, entry) { +export const saveDeviceLog = function(serial, entry) { + return trace('saveDeviceLog', {serial, entry}, () => { + return db.connect().then(client => { + return client.collection('logs').insertOne({ + id: uuid.v4() + , serial: serial + , timestamp: new Date(entry.timestamp) + , priority: entry.priority + , tag: entry.tag + , pid: entry.pid + , message: entry.message + } + ) + }) + }) +} + +// dbapi.saveDeviceInitialState = function(serial, device) { +export const saveDeviceInitialState = function(serial, device) { + return trace('saveDeviceInitialState', {serial, device}, () => { + let data = { + present: true + , presenceChangedAt: getNow() + , provider: device.provider + , owner: null + , status: 1 + , statusChangedAt: getNow() + , bookedBefore: 0 + , ready: true + , reverseForwards: [] + , remoteConnect: false + , remoteConnectUrl: null + , usage: null + , logs_enabled: false + , ...device + } + return db.connect().then(client => { + return client.collection('devices').updateOne({serial: serial}, + { + $set: data + } + ) + .then(stats => { + if (stats.modifiedCount === 0 && stats.matchedCount === 0) { + return getRootGroup().then(function(group) { + data.serial = serial + data.createdAt = getNow() + data.group = { + id: group.id + , name: group.name + , lifeTime: group.dates[0] + , owner: group.owner + , origin: group.id + , class: group.class + , repetitions: group.repetitions + , originName: group.name + , lock: false + } + return client.collection('devices').insertOne(data) + .then(() => { + return addOriginGroupDevice(group, serial) + }) + }) + } + return true + }) + .then(() => { + return client.collection('devices').findOne({serial: serial}) + }) + }) + }) +} + +// dbapi.setDeviceConnectUrl = function(serial, url) { +export const setDeviceConnectUrl = function(serial, url) { + return trace('setDeviceConnectUrl', {serial, url}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: { + remoteConnectUrl: url + , remoteConnect: true + } + } + ) + }) + }) +} + +// dbapi.unsetDeviceConnectUrl = function(serial) { +export const unsetDeviceConnectUrl = function(serial) { + return trace('unsetDeviceConnectUrl', {serial}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: { + remoteConnectUrl: null + , remoteConnect: false + } + } + ) + }) + }) +} + +// dbapi.saveDeviceStatus = function(serial, status) { +export const saveDeviceStatus = function(serial, status) { + return trace('saveDeviceStatus', {serial, status}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: { + status: status + , statusChangedAt: getNow() + } + } + ) + }) + }) +} + +// dbapi.enhanceStatusChangedAt = function(serial, timeout) { +export const enhanceStatusChangedAt = function(serial, timeout) { + return trace('enhanceStatusChangedAt', {serial, timeout}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: { + statusChangedAt: getNow() + , bookedBefore: timeout + } + } + ) + }) + }) +} + +// dbapi.setDeviceOwner = function(serial, owner) { +export const setDeviceOwner = function(serial, owner) { + return trace('setDeviceOwner', {serial, owner}, () => { + log.info('Setting device owner in db - ' + owner.email) + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: {owner: owner} + } + ) + }) + }) +} + +// dbapi.setDevicePlace = function(serial, place) { +export const setDevicePlace = function(serial, place) { + return trace('setDevicePlace', {serial, place}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: {place: place} + } + ) + }) + }) +} + +// dbapi.setDeviceStorageId = function(serial, storageId) { +export const setDeviceStorageId = function(serial, storageId) { + return trace('setDeviceStorageId', {serial, storageId}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: {storageId: storageId} + } + ) + }) + }) +} + + +// dbapi.unsetDeviceOwner = function(serial) { +export const unsetDeviceOwner = function(serial) { + return trace('unsetDeviceOwner', {serial}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: {owner: null} + } + ) + }) + }) +} + +// dbapi.setDevicePresent = function(serial) { +export const setDevicePresent = function(serial) { + return trace('setDevicePresent', {serial}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: { + present: true + , presenceChangedAt: getNow() + } + } + ) + }) + }) +} + +// dbapi.setDeviceAbsent = function(serial) { +export const setDeviceAbsent = function(serial) { + return trace('setDeviceAbsent', {serial}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: { + owner: null + , present: false + , presenceChangedAt: getNow() + } + } + ) + }) + }) +} + +// dbapi.setDeviceUsage = function(serial, usage) { +export const setDeviceUsage = function(serial, usage) { + return trace('setDeviceUsage', {serial, usage}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: { + usage: usage + , usageChangedAt: getNow() + } + } + ) + }) + }) +} + +// dbapi.unsetDeviceUsage = function(serial) { +export const unsetDeviceUsage = function(serial) { + return trace('unsetDeviceUsage', {serial}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: { + usage: null + , usageChangedAt: getNow() + , logs_enabled: false + } + } + ) + }) + }) +} + +// dbapi.setDeviceAirplaneMode = function(serial, enabled) { +export const setDeviceAirplaneMode = function(serial, enabled) { + return trace('setDeviceAirplaneMode', {serial, enabled}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: { + airplaneMode: enabled + } + } + ) + }) + }) +} + +// dbapi.setDeviceBattery = function(serial, battery) { +export const setDeviceBattery = function(serial, battery) { + return trace('setDeviceBattery', {serial, battery}, () => { + const batteryData = { + status: battery.status + , health: battery.health + , source: battery.source + , level: battery.level + , scale: battery.scale + , temp: battery.temp + , voltage: battery.voltage + } + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: {battery: batteryData} + } + ) + }) + }) +} + +// dbapi.setDeviceBrowser = function(serial, browser) { +export const setDeviceBrowser = function(serial, browser) { + return trace('setDeviceBrowser', {serial, browser}, () => { + const browserData = { + selected: browser.selected + , apps: browser.apps + } + + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: {browser: browserData} + } + ) + }) + }) +} + +// dbapi.setDeviceServicesAvailability = function(serial, service) { +export const setDeviceServicesAvailability = function(serial, service) { + return trace('setDeviceServicesAvailability', {serial, service}, () => { + const serviceData = { + hasHMS: service.hasHMS + , hasGMS: service.hasGMS + } + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: {service: serviceData} + } + ) + }) + }) +} + +// dbapi.setDeviceConnectivity = function(serial, connectivity) { +export const setDeviceConnectivity = function(serial, connectivity) { + return trace('setDeviceConnectivity', {serial, connectivity}, () => { + const networkData = { + connected: connectivity.connected + , type: connectivity.type + , subtype: connectivity.subtype + , failover: !!connectivity.failover + , roaming: !!connectivity.roaming + } + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: {network: networkData} + } + ) + }) + }) +} + +// dbapi.setDevicePhoneState = function(serial, state) { +export const setDevicePhoneState = function(serial, state) { + return trace('setDevicePhoneState', {serial, state}, () => { + const networkData = { + state: state.state + , manual: state.manual + , operator: state.operator + } + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: {network: networkData} + } + ) + }) + }) +} + +// dbapi.setDeviceRotation = function(serial, rotation) { +export const setDeviceRotation = function(message) { + return trace('setDeviceRotation', {message}, () => { + return db.connect().then(client => { + let setObj = { + 'display.rotation': message.rotation + } + if (message.height !== null) { + setObj['display.height'] = message.height + setObj['display.width'] = message.width + } + return client.collection('devices').updateOne( + {serial: message.serial}, + { + $set: setObj + } + ) + }) + }) +} + +// dbapi.setDeviceNote = function(serial, note) { +export const setDeviceNote = function(serial, note) { + return trace('setDeviceNote', {serial, note}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: {notes: note} + } + ) + }) + }) +} + +// dbapi.setDeviceReverseForwards = function(serial, forwards) { +export const setDeviceReverseForwards = function(serial, forwards) { + return trace('setDeviceReverseForwards', {serial, forwards}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: {reverseForwards: forwards} + } + ) + }) + }) +} + +// dbapi.setDeviceReady = function(serial, channel) { +export const setDeviceReady = function(serial, channel) { + return trace('setDeviceReady', {serial, channel}, () => { + const data = { + channel: channel + , ready: true + , owner: null + , present: true + , reverseForwards: [] + } + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: data + } + ) + }) + }) +} + +// dbapi.saveDeviceIdentity = function(serial, identity) { +export const saveDeviceIdentity = function(serial, identity) { + return trace('saveDeviceIdentity', {serial, identity}, () => { + const identityData = { + platform: identity.platform + , manufacturer: identity.manufacturer + , operator: identity.operator + , model: identity.model + , version: identity.version + , abi: identity.abi + , sdk: identity.sdk + , display: identity.display + , phone: identity.phone + , product: identity.product + , cpuPlatform: identity.cpuPlatform + , openGLESVersion: identity.openGLESVersion + , marketName: identity.marketName + , macAddress: identity.macAddress + , ram: identity.ram + } + + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: identityData + } + ) + }) + }) +} + +// dbapi.loadDevices = function(groups) { +export const loadDevices = function(groups) { + return trace('loadDevices', {groups}, () => { + return db.connect().then(client => { + if (groups.length > 0) { + return client.collection('devices').find( + { + 'group.id': {$in: groups} + } + ).toArray() + } + else { + return client.collection('devices').find().toArray() + } + }) + }) +} + +// dbapi.loadDevicesByOrigin = function(groups) { +export const loadDevicesByOrigin = function(groups) { + return trace('loadDevicesByOrigin', {groups}, () => { + return db.connect().then(client => { + return client.collection('devices').find( + {'group.origin': {$in: groups}} + ).toArray() + }) + }) +} + +// dbapi.loadBookableDevices = function(groups) { +export const loadBookableDevices = function(groups) { + return trace('loadBookableDevices', {groups}, () => { + return db.connect().then(client => { + return client.collection('devices').find( + { + $and: [ + {'group.origin': {$in: groups}} + , {present: {$eq: true}} + , {ready: {$eq: true}} + , {owner: {$eq: null}} + ] + } + ).toArray() + }) + }) +} + +export const loadBookableDevicesWithFiltersLock = function(groups, abi, model, type, sdk, version, devicesFunc, limit = null) { + return trace('loadBookableDevicesWithFiltersLock', {groups, abi, model, type, sdk, version, devicesFunc, limit}, () => { + let filterOptions = [] + let serials = [] + if (abi) { + filterOptions.push({abi: {$eq: abi}}) + } + if (model) { + filterOptions.push({model: {$eq: model}}) + } + if (sdk) { + filterOptions.push({sdk: {$eq: sdk}}) + } + if (version) { + filterOptions.push({version: {$eq: version}}) + } + if (type) { + filterOptions.push({type: {$eq: type}}) + } + return db.connect().then(client => { + let result = client.collection('devices').find( + { + $and: [ + {'group.origin': {$in: groups}} + , {'group.class': {$eq: apiutil.BOOKABLE}} + , {present: {$eq: true}} + , {ready: {$eq: true}} + , {owner: {$eq: null}} + , {'group.lock': {$eq: false}} + , ...filterOptions + ] + } + ) + if (limit) { + result = result.limit(limit) + } + return result.toArray() + .then(devices => { + serials = devices.map(device => device.serial) + lockDevices(serials).then(() => { + return devicesFunc(devices) + }) + .finally(() => { + if (serials.length > 0) { + unlockDevices(serials) + } + }) + }) + }) + }) +} + +// dbapi.loadStandardDevices = function(groups) { +export const loadStandardDevices = function(groups) { + return trace('loadStandardDevices', {groups}, () => { + return db.connect().then(client => { + return client.collection('devices') + .find({ + 'group.class': apiutil.STANDARD + , 'group.id': {$in: groups} + }) + .toArray() + }) + }) +} + +// dbapi.loadPresentDevices = function() { +export const loadPresentDevices = function() { + return trace('loadPresentDevices', {}, () => { + return db.connect().then(client => { + return client.collection('devices').find({present: true}) + }) + }) +} + +// dbapi.loadDeviceBySerial = function(serial) { +export const loadDeviceBySerial = function(serial) { + return trace('loadDeviceBySerial', {serial}, () => { + return db.connect().then(client => { + return client.collection('devices').findOne({serial: serial}) + }) + }) +} + +// dbapi.loadDevicesBySerials = function(serials) { +export const loadDevicesBySerials = function(serials) { + return trace('loadDevicesBySerials', {serials}, () => { + return db.connect().then(client => { + return client.collection('devices').find({serial: {$in: serials}}).toArray() + }) + }) +} + +// dbapi.loadDevice = function(groups, serial) { +export const loadDevice = function(groups, serial) { + return trace('loadDevice', {groups, serial}, () => { + return db.connect().then(client => { + return client.collection('devices').findOne( + { + serial: serial + , 'group.id': {$in: groups} + } + ) + }) + }) +} + +// dbapi.loadBookableDevice = function(groups, serial) { +export const loadBookableDevice = function(groups, serial) { + return trace('loadBookableDevice', {groups, serial}, () => { + return db.connect().then(client => { + return client.collection('devices') + .find( + { + serial: serial + , 'group.origin': {$in: groups} + , 'group.class': {$ne: apiutil.STANDARD} + } + ) + .toArray() + }) + }) +} + +// dbapi.loadDeviceByCurrent = function(groups, serial) { +export const loadDeviceByCurrent = function(groups, serial) { + return trace('loadDeviceByCurrent', {groups, serial}, () => { + return db.connect().then(client => { + return client.collection('devices') + .find( + { + serial: serial + , 'group.id': {$in: groups} + } + ) + .toArray() + }) + }) +} + +// dbapi.loadDeviceByOrigin = function(groups, serial) { +export const loadDeviceByOrigin = function(groups, serial) { + return trace('loadDeviceByOrigin', {groups, serial}, () => { + return db.connect().then(client => { + return client.collection('devices') + .find( + { + serial: serial + , 'group.origin': {$in: groups} + } + ) + .toArray() + }) + }) +} + +// dbapi.saveUserAccessToken = function(email, token) { +export const saveUserAccessToken = function(email, token) { + return trace('saveUserAccessToken', {email, token}, () => { + return db.connect().then(client => { + return client.collection('accessTokens').insertOne( + { + email: email + , id: token.id + , title: token.title + , jwt: token.jwt + } + ) + }) + }) +} + +// dbapi.removeUserAccessTokens = function(email) { +export const removeUserAccessTokens = function(email) { + return trace('removeUserAccessTokens', {email}, () => { + return db.connect().then(client => { + return client.collection('accessTokens').deleteMany( + { + email: email + } + ) + }) + }) +} + +// dbapi.removeUserAccessToken = function(email, title) { +export const removeUserAccessToken = function(email, title) { + return trace('removeUserAccessToken', {email, title}, () => { + return db.connect().then(client => { + return client.collection('accessTokens').deleteOne( + { + email: email + , title: title + } + ) + }) + }) +} + +// dbapi.removeAccessToken = function(id) { +export const removeAccessToken = function(id) { + return trace('removeAccessToken', {id}, () => { + return db.connect().then(client => { + return client.collection('accessTokens').deleteOne({id: id}) + }) + }) +} + +// dbapi.loadAccessTokens = function(email) { +export const loadAccessTokens = function(email) { + return trace('loadAccessTokens', {email}, () => { + return db.connect().then(client => { + return client.collection('accessTokens').find({email: email}).toArray() + }) + }) +} + +// dbapi.loadAccessToken = function(id) { +export const loadAccessToken = function(id) { + return trace('loadAccessToken', {id}, () => { + return db.connect().then(client => { + return client.collection('accessTokens').findOne({id: id}) + }) + }) +} + +// dbapi.grantAdmin = function(email) { +export const grantAdmin = function(email) { + return trace('grantAdmin', {email}, () => { + return db.connect().then(client => { + return client.collection('users').updateOne({email: email}, + { + $set: { + privilege: apiutil.ADMIN + } + }) + }) + }) +} + +// dbapi.revokeAdmin = function(email) { +export const revokeAdmin = function(email) { + return trace('revokeAdmin', {email}, () => { + return db.connect().then(client => { + return client.collection('users').updateOne({email: email}, + { + $set: { + privilege: apiutil.USER + } + }) + }) + }) +} + +// dbapi.makeOriginGroupBookable = function() { +export const makeOriginGroupBookable = function() { + return trace('makeOriginGroupBookable', {}, () => { + return db.connect().then(client => { + return client.collection('groups').updateOne( + { + name: 'Common' + } + , { + $set: { + class: apiutil.BOOKABLE + } + } + ) + }) + }) +} + +// dbapi.acceptPolicy = function(email) { +export const acceptPolicy = function(email) { + return trace('acceptPolicy', {email}, () => { + return db.connect().then(client => { + return client.collection('users').updateOne({email: email}, + { + $set: { + acceptedPolicy: true + } + }) + }) + }) +} + +// dbapi.writeStats = function(user, serial, action) { +export const writeStats = function(user, serial, action) { + return trace('writeStats', {user, serial, action}, () => { + return db.connect().then(client => { + return client.collection('stats').insertOne({ + id: (uuid.v4() + '_' + user.email + '_' + user.group) + , user: user.email + , action: action + , device: serial + , timestamp: getNow() + } + ) + }) + }) +} + +// dbapi.getDevicesCount = function() { +export const getDevicesCount = function() { + return trace('getDevicesCount', {}, () => { + return db.connect().then(client => { + return client.collection('devices').find().count() + }) + }) +} + +// dbapi.getOfflineDevicesCount = function() { +export const getOfflineDevicesCount = function() { + return trace('getOfflineDevicesCount', {}, () => { + return db.connect().then(client => { + return client.collection('devices').find( + { + present: false + } + ).count() + }) + }) +} + +// dbapi.getOfflineDevices = function() { +export const getOfflineDevices = function() { + return trace('getOfflineDevices', {}, () => { + return db.connect().then(client => { + return client.collection('devices').find( + {present: false}, + {_id: 0, 'provider.name': 1} + ).toArray() + }) + }) +} + +// dbapi.isPortExclusive = function(newPort) { +export const isPortExclusive = function(newPort) { + return trace('isPortExclusive', {newPort}, () => { + return getAllocatedAdbPorts().then((ports) => { + let result = !!ports.find(port => port === newPort) + return !result + }) + }) +} + +// dbapi.getLastAdbPort = function() { +export const getLastAdbPort = function() { + return trace('getLastAdbPort', {}, () => { + return getAllocatedAdbPorts().then((ports) => { + if (ports.length === 0) { + return 0 + } + return Math.max(...ports) + }) + }) +} + +// dbapi.getAllocatedAdbPorts = function() { +export const getAllocatedAdbPorts = function() { + return trace('getAllocatedAdbPorts', {}, () => { + return db.connect().then(client => { + return client.collection('devices').find({}, {adbPort: 1, _id: 0}).toArray().then(ports => { + let result = [] + ports.forEach((port) => { + if (port.adbPort) { + let portNum + if (typeof port.adbPort === 'string') { + portNum = parseInt(port.adbPort.replace('"', '').replace('\'', ''), 10) + } + else { + portNum = port.adbPort + } + result.push(portNum) + } + }) + return result.sort((a, b) => a - b) + }) + }) + }) +} + +// dbapi.initiallySetAdbPort = function(serial) { +export const initiallySetAdbPort = function(serial) { + return trace('initiallySetAdbPort', {serial}, () => { + return getFreeAdbPort().then((port) => { + if (port) { + return setAdbPort(serial, port) + } + else { + return null + } + }) + }) +} + +// dbapi.setAdbPort = function(serial, port) { +export const setAdbPort = function(serial, port) { + return trace('setAdbPort', {serial, port}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne({serial: serial}, {$set: {adbPort: port}}).then(() => { + return port + }) + }) + }) +} + +// dbapi.getAdbRange = function() { +export const getAdbRange = function() { + return trace('getAdbRange', {}, () => { + return db.getRange() + }) +} + +// dbapi.getFreeAdbPort = function() { +export const getFreeAdbPort = function() { + return trace('getFreeAdbPort', {}, () => { + const adbRange = getAdbRange().split('-') + const adbRangeStart = parseInt(adbRange[0], 10) + const adbRangeEnd = parseInt(adbRange[1], 10) + + return getLastAdbPort().then((lastPort) => { + if (lastPort === 0) { + return adbRangeStart + } + let freePort = lastPort + 1 + if (freePort > adbRangeEnd || freePort <= adbRangeStart) { + log.error('Port: ' + freePort + ' out of range [' + adbRangeStart + ':' + adbRangeEnd + ']') + return null + } + + return isPortExclusive(freePort).then((result) => { + if (result) { + return freePort + } + else { + log.error('Port: ' + freePort + ' not exclusive.') + return null + } + }) + }) + }) +} + +// dbapi.generateIndexes = function() { +export const generateIndexes = function() { + return trace('generateIndexes', {}, () => { + return db.connect().then(client => { + client.collection('devices').createIndex({serial: -1}, function(err, result) { + log.info('Created indexes with result - ' + result) + }) + }) + }) +} + +// dbapi.setDeviceSocketDisplay = function(data) { +export const setDeviceSocketDisplay = function(data) { + return trace('setDeviceSocketDisplay', {data}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: data.serial}, + { + $set: { + 'display.density': 2 + , 'display.fps': 60 + , 'display.id': 0 + , 'display.rotation': 0 + , 'display.secure': true + , 'display.size': 4.971253395080566 + , 'display.xdpi': 294.9670104980469 + , 'display.ydpi': 295.56298828125 + , 'display.width': data.width + , 'display.height': data.height + } + } + ).then(() => { + loadDeviceBySerial(data.serial) + }) + }) + }) +} + +// dbapi.setDeviceSocketPorts = function(data, publicIp) { +export const setDeviceSocketPorts = function(data, publicIp) { + return trace('setDeviceSocketPorts', {data, publicIp}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: data.serial}, + { + $set: { + 'display.url': `ws://${publicIp}:${data.screenPort}/` + , 'display.screenPort': data.screenPort + , 'display.connectPort': data.connectPort + } + } + ).then(() => { + loadDeviceBySerial(data.serial) + }) + }) + }) +} + +// dbapi.updateIosDevice = function(message) { +export const updateIosDevice = function(message) { + return trace('updateIosDevice', {message}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + { + serial: message.id + }, + { + $set: { + id: message.id + , model: message.name + , platform: message.platform + , sdk: message.architect + } + } + ) + }) + }) +} + +// dbapi.setDeviceIosVersion = function(message) { +export const setDeviceIosVersion = function(message) { + return trace('setDeviceIosVersion', {message}, () => { + const data = { + version: message.sdkVersion + } + + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: message.id}, + { + $set: data + } + ) + }) + }) +} + +// dbapi.sizeIosDevice = function(serial, height, width, scale) { +export const sizeIosDevice = function(serial, height, width, scale) { + return trace('sizeIosDevice', {serial, height, width, scale}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: serial}, + { + $set: { + 'display.scale': scale + , 'display.height': height + , 'display.width': width + } + } + ) + }) + }) +} + +// dbapi.getDeviceDisplaySize = function(serial) { +export const getDeviceDisplaySize = function(serial) { + return trace('getDeviceDisplaySize', {serial}, () => { + return db.connect().then(client => { + return client.collection('devices').findOne({serial: serial}) + .then(result => { + return result.display + }) + }) + }) +} + +export const setAbsentDisconnectedDevices = function() { + return trace('setAbsentDisconnectedDevices', {}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + { + ios: true + }, + { + $set: { + present: false + , ready: false + } + } + ) + }) + }) +} + +/* // @TODO refactor setDeviceApp +/!** + * + * @param message obj with application options + * @method check if exists app with equal option + * if exists replace it or append to installedApps list + * + *!/ +// dbapi.setDeviceApp = function(message) { +export const setDeviceApp = function(message) { + return dbapi.loadDeviceBySerial(message.serial) + .then(result => { + let removePathApp = '' + let {installedApps} = result + let index = installedApps.findIndex(item => { + return ( + item.bundleName === message.bundleName && + item.bundleId === message.bundleId + ) + }) + if(index >= 0) { + removePathApp = installedApps[index].pathToApp + + installedApps[index] = { + bundleId: message.bundleId + , bundleName: message.bundleName + , pathToApp: message.pathToApp + } + db.run(r.table('devices').get(message.serial).update({ + installedApps + })) + return Promise.resolve({removePathApp}) + } + else { + db.run(r.table('devices').get(message.serial).update({ + installedApps: r.row('installedApps').default([]).append({ + bundleId: message.bundleId + , bundleName: message.bundleName + , pathToApp: message.pathToApp + }) + })) + return Promise.resolve({removePathApp: ''}) + } + }) + .catch(err => { + db.run(r.table('devices').get(message.serial).update({ + installedApps: r.row('installedApps').default([]).append({ + bundleId: message.bundleId + , bundleName: message.bundleName + , pathToApp: message.pathToApp + }) + })) + return Promise.reject(err) + }) +}*/ + +// dbapi.getInstalledApplications = function(message) { +export const getInstalledApplications = function(message) { + return trace('getInstalledApplications', {message}, () => { + return loadDeviceBySerial(message.serial) + }) +} + +// dbapi.getDeviceGroupOwner = function(serial) { +export const getDeviceGroupOwner = function(serial) { + return trace('getDeviceGroupOwner', {serial}, () => { + return db.connect().then(client => { + return client.collection('devices').findOne({serial: serial}) + .then(result => { + return result.group.owner + }) + }) + }) +} + +// dbapi.setDeviceGroupOwner = function(message) { +export const setDeviceGroupOwner = function(message) { + return trace('setDeviceGroupOwner', {message}, () => { + let data = { + 'group.owner.email': process.env.STF_ADMIN_EMAIL || 'administrator@fakedomain.com' + , 'group.owner.name': process.env.STF_ADMIN_NAME || 'administrator' + } + return db.connect().then(client => { + return client.collection('devices').updateOne( + {serial: message.serial}, + { + $set: data + } + ) + }) + }) +} + +// dbapi.setDeviceType = function(serial, type) { +export const setDeviceType = function(serial, type) { + return trace('setDeviceType', {serial, type}, () => { + return db.connect().then(client => { + return client.collection('devices').updateOne( + { + serial: serial + }, + { + $set: { + deviceType: type + } + } + ) + }) + }) +} + +// dbapi.getDeviceType = function(serial) { +export const getDeviceType = function(serial) { + return trace('getDeviceType', {serial}, () => { + return db.connect().then(client => { + return client.collection('devices').findOne({serial: serial}) + .then(result => { + return result.deviceType + }) + }) + }) +} + +// dbapi.initializeIosDeviceState = function(publicIp, message) { +export const initializeIosDeviceState = function(publicIp, message) { + return trace('initializeIosDeviceState', {publicIp, message}, () => { + const screenWsUrlPattern = + message.provider.screenWsUrlPattern || `ws://${publicIp}:${message.ports.screenPort}/` + + const data = { + present: true + , presenceChangedAt: getNow() + , provider: message.provider + , owner: null + , status: message.status + , statusChangedAt: getNow() + , ready: true + , reverseForwards: [] + , remoteConnect: false + , remoteConnectUrl: null + , usage: null + , ios: true + , display: { + density: DEFAULT_IOS_DEVICE_ARGS.DENSITY + , fps: DEFAULT_IOS_DEVICE_ARGS.FPS + , id: DEFAULT_IOS_DEVICE_ARGS.ID + , rotation: DEFAULT_IOS_DEVICE_ARGS.ROTATION + , secure: DEFAULT_IOS_DEVICE_ARGS.SECURE + , size: DEFAULT_IOS_DEVICE_ARGS.SIZE + , xdpi: DEFAULT_IOS_DEVICE_ARGS.XDPI + , ydpi: DEFAULT_IOS_DEVICE_ARGS.YDPI + , url: screenWsUrlPattern + } + , 'group.owner.email': process.env.STF_ADMIN_EMAIL || 'administrator@fakedomain.com' + , 'group.owner.name': process.env.STF_ADMIN_NAME || 'administrator' + , screenPort: message.ports.screenPort + , connectPort: message.ports.connectPort + , model: message.options.name + , platform: message.options.platform + , sdk: message.options.architect + , abi: 'arm64' + } + + return db.connect().then(client => { + return client.collection('devices').updateOne({serial: message.serial}, + { + $set: data + } + ) + .then(stats => { + if (stats.modifiedCount === 0 && stats.matchedCount === 0) { + return getRootGroup().then(function(group) { + data.serial = message.serial + data.createdAt = getNow() + data.group = { + id: group.id + , name: group.name + , lifeTime: group.dates[0] + , owner: group.owner + , origin: group.id + , class: group.class + , repetitions: group.repetitions + , originName: group.name + , lock: false + } + return client.collection('devices').insertOne(data) + .then(() => { + return addOriginGroupDevice(group, message.serial) + }) + }) + } + return true + }) + .then(() => { + return client.collection('devices').findOne({serial: message.serial}) + }) + }) + }) +} + +export default { + DuplicateSecondaryIndexError + , unlockBookingObjects + , getNow + , createBootStrap + , deleteDevice + , deleteUser + , getReadyGroupsOrderByIndex + , getGroupsByIndex + , getGroupByIndex + , getGroupsByUser + , getGroup + , getGroups + , getUsers + , getEmails + , addGroupUser + , getAdmins + , addAdminsToGroup + , removeGroupUser + , lockDeviceByCurrent + , lockDeviceByOrigin + , addOriginGroupDevice + , removeOriginGroupDevice + , addGroupDevices + , removeGroupDevices + , lockDevice + , lockDevices + , unlockDevice + , unlockDevices + , setLockOnDevices + , lockUser + , unlockUser + , lockGroupByOwner + , lockGroup + , unlockGroup + , adminLockGroup + , adminUnlockGroup + , getRootGroup + , getUserGroup + , getUserGroups + , getOnlyUserGroups + , getTransientGroups + , getDeviceTransientGroups + , isDeviceBooked + , isRemoveGroupUserAllowed + , isUpdateDeviceOriginGroupAllowed + , getDeviceGroups + , getGroupAsOwnerOrAdmin + , getOwnerGroups + , createGroup + , createUserGroup + , updateGroup + , reserveUserGroupInstance + , releaseUserGroupInstance + , updateUserGroupDuration + , updateUserGroupsQuotas + , updateDefaultUserGroupsQuotas + , updateDeviceGroupName + , updateDeviceCurrentGroupFromOrigin + , askUpdateDeviceOriginGroup + , updateDeviceOriginGroup + , updateDeviceCurrentGroup + , updateDevicesCurrentGroup + , returnDeviceToOriginGroup + , returnDevicesToOriginGroup + , updateUserGroup + , deleteGroup + , deleteUserGroup + , createUser + , saveUserAfterLogin + , loadUser + , updateUsersAlertMessage + , updateUserSettings + , resetUserSettings + , insertUserAdbKey + , deleteUserAdbKey + , lookupUsersByAdbKey + , lookupUserByAdbFingerprint + , lookupUserByVncAuthResponse + , loadUserDevices + , saveDeviceLog + , saveDeviceInitialState + , setDeviceConnectUrl + , unsetDeviceConnectUrl + , saveDeviceStatus + , enhanceStatusChangedAt + , setDeviceOwner + , setDevicePlace + , setDeviceStorageId + , unsetDeviceOwner + , setDevicePresent + , setDeviceAbsent + , setDeviceUsage + , unsetDeviceUsage + , setDeviceAirplaneMode + , setDeviceBattery + , setDeviceBrowser + , setDeviceServicesAvailability + , setDeviceConnectivity + , setDevicePhoneState + , setDeviceRotation + , setDeviceNote + , setDeviceReverseForwards + , setDeviceReady + , saveDeviceIdentity + , loadDevices + , loadDevicesByOrigin + , loadBookableDevices + , loadBookableDevicesWithFiltersLock + , loadStandardDevices + , loadPresentDevices + , loadDeviceBySerial + , loadDevicesBySerials + , loadDevice + , loadBookableDevice + , loadDeviceByCurrent + , loadDeviceByOrigin + , saveUserAccessToken + , removeUserAccessTokens + , removeUserAccessToken + , removeAccessToken + , loadAccessTokens + , loadAccessToken + , grantAdmin + , revokeAdmin + , makeOriginGroupBookable + , acceptPolicy + , writeStats + , getDevicesCount + , getOfflineDevicesCount + , getOfflineDevices + , isPortExclusive + , getLastAdbPort + , getAllocatedAdbPorts + , initiallySetAdbPort + , setAdbPort + , getAdbRange + , getFreeAdbPort + , generateIndexes + , setDeviceSocketDisplay + , setDeviceSocketPorts + , updateIosDevice + , setDeviceIosVersion + , sizeIosDevice + , getDeviceDisplaySize + , setAbsentDisconnectedDevices + // , setDeviceApp + , getInstalledApplications + , getDeviceGroupOwner + , setDeviceGroupOwner + , setDeviceType + , getDeviceType + , initializeIosDeviceState +} diff --git a/lib/db/api.mjs b/lib/db/api.mjs deleted file mode 100644 index 60c1d61806..0000000000 --- a/lib/db/api.mjs +++ /dev/null @@ -1,2872 +0,0 @@ -/** - * Copyright © 2024 contains code contributed by V Kontakte LLC, authors: Daniil Smirnov, Egor Platonov, Aleksey Chistov - Licensed under the Apache license 2.0 - **/ -import util from 'util' -import db from './index.mjs' -import wireutil from '../wire/util.js' -import uuid from 'uuid' -import apiutil from '../util/apiutil.js' -import Promise from 'bluebird' -import _ from 'lodash' - -import logger from '../util/logger.js' - -const log = logger.createLogger('dbapi') - -const dbapi = Object.create(null) - -// constant default device data - -const DEFAULT_IOS_DEVICE_ARGS = { - DENSITY: 2 - , FPS: 60 - , ID: 0 - , ROTATION: 0 - , SECURE: true - , SIZE: 4.971253395080566 - , XDPI: 294.9670104980469 - , YDPI: 295.56298828125 -} - -// - -// dbapi. -dbapi.DuplicateSecondaryIndexError = function DuplicateSecondaryIndexError() { - Error.call(this) - this.name = 'DuplicateSecondaryIndexError' - Error.captureStackTrace(this, DuplicateSecondaryIndexError) -} - -util.inherits(dbapi.DuplicateSecondaryIndexError, Error) - - -dbapi.unlockBookingObjects = function() { - return db.connect().then(client => { - return Promise.all([ - client.collection('users').updateMany( - {}, - { - $set: {'groups.lock': false} - } - ) - , client.collection('devices').updateMany( - {}, - { - $set: {'group.lock': false} - } - ) - , client.collection('groups').updateMany( - {}, - { - $set: { - 'lock.user': false - , 'lock.admin': false - } - } - ) - ]) - }) -} - -dbapi.getNow = function() { - return new Date() -} - - -dbapi.createBootStrap = function(env) { - const now = Date.now() - - function updateUsersForMigration(group) { - return dbapi.getUsers().then(function(users) { - return Promise.map(users, function(user) { - return db.connect().then(client => { - let data = { - privilege: user.email !== group.owner.email ? apiutil.USER : apiutil.ADMIN - , 'groups.subscribed': [] - , 'groups.lock': false - , 'groups.quotas.allocated.number': group.envUserGroupsNumber - , 'groups.quotas.allocated.duration': group.envUserGroupsDuration - , 'groups.quotas.consumed.duration': 0 - , 'groups.quotas.consumed.number': 0 - , 'groups.defaultGroupsNumber': user.email !== group.owner.email ? 0 : group.envUserGroupsNumber - , 'groups.defaultGroupsDuration': user.email !== group.owner.email ? 0 : group.envUserGroupsDuration - , 'groups.defaultGroupsRepetitions': user.email !== group.owner.email ? 0 : group.envUserGroupsRepetitions - , 'groups.repetitions': group.envUserGroupsRepetitions - } - client.collection('users').updateOne( - {email: user.email}, - { - $set: data - } - ) - .then(function(stats) { - if (stats.modifiedCount > 0) { - return dbapi.addGroupUser(group.id, user.email) - } - return stats - }) - }) - }) - }) - } - - function getDevices() { - return db.connect().then(client => { - return client.collection('devices').find().toArray() - }) - } - - function updateDevicesForMigration(group) { - return getDevices().then(function(devices) { - return Promise.map(devices, function(device) { - log.info('Migrating device ' + device.serial) - return db.connect().then(client => { - let data = { - 'group.id': group.id - , 'group.name': group.name - , 'group.lifeTime': group.lifeTime - , 'group.owner': group.owner - , 'group.origin': group.origin - , 'group.class': group.class - , 'group.repetitions': group.repetitions - , 'group.originName': group.originName - , 'group.lock': false - } - return client.collection('devices').updateOne( - {serial: device.serial}, - { - $set: data - } - ).then(function(stats) { - if (stats.modifiedCount > 0) { - return dbapi.addOriginGroupDevice(group, device.serial) - } - return stats - }) - }) - }) - }) - } - - return dbapi.createGroup({ - name: env.STF_ROOT_GROUP_NAME - , owner: { - email: env.STF_ADMIN_EMAIL - , name: env.STF_ADMIN_NAME - } - , users: [env.STF_ADMIN_EMAIL] - , privilege: apiutil.ROOT - , class: apiutil.BOOKABLE - , repetitions: 0 - , duration: 0 - , isActive: true - , state: apiutil.READY - , dates: [{ - start: new Date(now) - , stop: new Date(now + apiutil.TEN_YEARS) - }] - , envUserGroupsNumber: apiutil.MAX_USER_GROUPS_NUMBER - , envUserGroupsDuration: apiutil.MAX_USER_GROUPS_DURATION - , envUserGroupsRepetitions: apiutil.MAX_USER_GROUPS_REPETITIONS - }) - .then(function(group) { - return dbapi.saveUserAfterLogin({ - name: group.owner.name - , email: group.owner.email - , ip: '127.0.0.1' - }) - .then(function() { - return updateUsersForMigration(group) - }) - .then(function() { - return updateDevicesForMigration(group) - }) - .then(function() { - return dbapi.reserveUserGroupInstance(group.owner.email) - }) - }) -} - -dbapi.deleteDevice = function(serial) { - return db.connect().then(client => { - return client.collection('devices').deleteOne({serial: serial}) - }) -} - -dbapi.deleteUser = function(email) { - return db.connect().then(client => { - return client.collection('users').deleteOne({email: email}) - }) -} - -dbapi.getReadyGroupsOrderByIndex = function(index) { - const options = { - // Sort matched documents in descending order by rating - sort: [index], - } - return db.connect().then(client => { - return client.collection('groups') - .find( - { - state: - { - $ne: apiutil.PENDING - } - } - , options - ) - .toArray() - }) -} - -dbapi.getGroupsByIndex = function(value, index) { - let findIndex = {} - findIndex[index] = value - return db.connect().then(client => { - return client.collection('groups').find(findIndex).toArray() - }) -} - - -dbapi.getGroupByIndex = function(value, index) { - return dbapi.getGroupsByIndex(value, index) - .then(function(array) { - return array[0] - }) -} - -dbapi.getGroupsByUser = function(email) { - return db.connect().then(client => { - return client.collection('groups').find({users: {$in: [email]}}).toArray() - }) -} - -dbapi.getGroup = function(id) { - return db.connect().then(client => { - return client.collection('groups').findOne({id: id}) - }) -} - -dbapi.getGroups = function() { - return db.connect().then(client => { - return client.collection('groups').find().toArray() - }) -} - -dbapi.getUsers = function() { - return db.connect().then(client => { - return client.collection('users').find().toArray() - }) -} - -dbapi.getEmails = function() { - return db.connect().then(client => { - return client.collection('users') - .find( - { - privilege: - { - $ne: apiutil.ADMIN - } - } - ) - .project({email: 1, _id: 0}) - .toArray() - }) -} - -dbapi.addGroupUser = function(id, email) { - return db.connect() - .then(client => { - return client.collection('groups').findOne({id: id}).then(group => { - return client.collection('users').findOne({email: email}).then(user => { - let newUsers, newSubs - if (group.users) { - newUsers = group.users - } - else { - newUsers = [] - } - - if (!newUsers.includes(email)) { - newUsers.push(email) - } - - if (user.groups.subscribed) { - newSubs = user.groups.subscribed - } - else { - newSubs = [] - } - - newSubs.push(id) - return Promise.all([ - client.collection('groups').updateOne( - { - id: id - }, - { - $set: { - users: newUsers - } - } - ) - , client.collection('users').updateOne( - { - email: email - }, - { - $set: { - 'groups.subscribed': newSubs - } - } - ) - ]) - }) - }) - }) - .then(function(stats) { - return stats[0].modifiedCount === 0 && stats[1].modifiedCount === 0 ? 'unchanged' : 'added' - }) -} - -dbapi.getAdmins = function() { - return db.connect().then(client => { - return client.collection('users') - .find({ - privilege: apiutil.ADMIN - }) - .project({email: 1, _id: 0}) - .toArray() - }) -} - - -dbapi.addAdminsToGroup = function(id) { - return dbapi.getAdmins().then(admins => { - return db.connect().then(client => { - return client.collection('groups').findOne({id: id}).then(group => { - admins.forEach((admin) => { - let newUsers = group.users - if (!newUsers.includes(admin.email)) { - newUsers.push(admin.email) - } - return client.collection('groups').updateOne( - { - id: id - }, - { - $set: { - users: newUsers - } - } - ).then(() => { - return client.collection('users').findOne({email: admin.email}) - .then(user => { - let newSubs = user.groups.subscribed - newSubs.push(id) - return client.collection('users').updateOne( - {email: admin.email}, - { - $set: { - 'groups.subscribed': newSubs - } - } - ) - }) - }) - }) - }) - }) - }) -} - -dbapi.removeGroupUser = function(id, email) { - return db.connect().then(client => { - return Promise.all([ - client.collection('groups').updateOne( - {id: id} - , [ - { - $set: { - users: { - $setDifference: ['$users', [email]] - } - } - } - ] - ) - , client.collection('users').updateOne( - {email: email} - , [ - { - $set: {'groups.subscribed': {$setDifference: ['$groups.subscribed', [id]]}} - } - ] - ) - ]) - }) - .then(function() { - return 'deleted' - }) -} - -dbapi.lockBookableDevice = function(groups, serial) { - function wrappedlockBookableDevice() { - return db.connect().then(client => { - return client.collection('devices').findOne({serial: serial}).then(oldDoc => { - return client.collection('devices').updateOne( - {serial: serial}, - [{ - $set: { - 'group.lock': { - $cond: [ - { - $and: [ - {$eq: ['$group.lock', false]} - , {$ne: ['$group.class', apiutil.STANDARD]} - , {$not: [{$eq: [{$setIntersection: [groups, ['$group.origin']]}, []]}]} - ] - } - , true - , '$group.lock' - ] - } - } - }] - ).then(updateStats => { - return client.collection('devices').findOne({serial: serial}).then(newDoc => { - updateStats.changes = [ - {new_val: {...newDoc}, old_val: {...oldDoc}} - ] - return updateStats - }) - }) - }) - }) - .then(function(stats) { - return apiutil.lockDeviceResult(stats, dbapi.loadBookableDevice, groups, serial) - }) - } - - return apiutil.setIntervalWrapper( - wrappedlockBookableDevice - , 10 - , Math.random() * 500 + 50) -} - -dbapi.lockDeviceByCurrent = function(groups, serial) { - function wrappedlockDeviceByCurrent() { - return db.connect().then(client => { - return client.collection('devices').findOne({serial: serial}).then(oldDoc => { - return client.collection('devices').updateOne( - {serial: serial}, - [{ - $set: { - 'group.lock': { - $cond: [ - { - $and: [ - {$eq: ['$group.lock', false]} - , {$not: [{$eq: [{$setIntersection: [groups, ['$group.id']]}, []]}]} - ] - } - , true - , '$group.lock' - ] - } - } - }] - ).then(updateStats => { - return client.collection('devices').findOne({serial: serial}).then(newDoc => { - updateStats.changes = [ - {new_val: {...newDoc}, old_val: {...oldDoc}} - ] - return updateStats - }) - }) - }) - }) - .then(function(stats) { - return apiutil.lockDeviceResult(stats, dbapi.loadDeviceByCurrent, groups, serial) - }) - } - - return apiutil.setIntervalWrapper( - wrappedlockDeviceByCurrent - , 10 - , Math.random() * 500 + 50) -} - -dbapi.lockDeviceByOrigin = function(groups, serial) { - function wrappedlockDeviceByOrigin() { - return db.connect().then(client => { - return client.collection('devices').findOne({serial: serial}).then(oldDoc => { - return client.collection('devices').updateOne( - {serial: serial}, - [{ - $set: { - 'group.lock': { - $cond: [ - { - $and: [ - {$eq: ['$group.lock', false]} - , {$not: [{$eq: [{$setIntersection: [groups, ['$group.origin']]}, []]}]} - ] - } - , true - , '$group.lock' - ] - } - } - }] - ).then(updateStats => { - return client.collection('devices').findOne({serial: serial}).then(newDoc => { - updateStats.changes = [ - {new_val: {...newDoc}, old_val: {...oldDoc}} - ] - return updateStats - }) - }) - }) - }) - .then(function(stats) { - return apiutil.lockDeviceResult(stats, dbapi.loadDeviceByOrigin, groups, serial) - }) - } - - return apiutil.setIntervalWrapper( - wrappedlockDeviceByOrigin - , 10 - , Math.random() * 500 + 50) -} - -dbapi.addOriginGroupDevice = function(group, serial) { - return db.connect().then(client => { - return client.collection('groups').findOne({id: group.id}).then((groupData) => { - let devices = groupData.devices - if (!devices.includes(serial)) { - devices.push(serial) - } - else { - return dbapi.getGroup(group.id) - } - return client.collection('groups').updateOne( - { - id: group.id - }, - { - $set: { - devices: devices - } - }).then(function() { - return dbapi.getGroup(group.id) - }) - }) - }) -} - -dbapi.removeOriginGroupDevice = function(group, serial) { - return db.connect().then(client => { - return client.collection('groups').updateOne( - {id: group.id} - , [ - {$set: {devices: {$setDifference: ['$devices', [serial]]}}} - ] - ) - .then(function() { - return dbapi.getGroup(group.id) - }) - }) -} - -dbapi.addGroupDevices = function(group, serials) { - const duration = apiutil.computeDuration(group, serials.length) - - return dbapi.updateUserGroupDuration(group.owner.email, group.duration, duration) - .then(function(stats) { - if (stats.modifiedCount > 0) { - return dbapi.updateGroup( - group.id - , { - duration: duration - , devices: _.union(group.devices, serials) - }) - .then((data) => { - if (group.class === apiutil.ONCE) { - return dbapi.updateDevicesCurrentGroup(serials, group) - .then(() => data) - } - return data - }) - } - return Promise.reject('quota is reached') - }) -} - -dbapi.removeGroupDevices = function(group, serials) { - const duration = apiutil.computeDuration(group, -serials.length) - - return dbapi.updateUserGroupDuration(group.owner.email, group.duration, duration) - .then(function() { - return dbapi.updateGroup( - group.id - , { - duration: duration - , devices: _.difference(group.devices, serials) - }) - }) -} - -function setLockOnDevice(serial, state) { - return db.connect().then(client => { - return client.collection('devices').findOne({serial: serial}).then(device => { - return client.collection('devices').updateOne({ - serial: serial - } - , - { - $set: {'group.lock': device.group.lock !== state ? state : device.group.lock} - } - ) - }) - }) -} - -dbapi.lockDevice = function(serial) { - return setLockOnDevice(serial, true) -} - -dbapi.lockDevices = function(serials) { - return dbapi.setLockOnDevices(serials, true) -} - -dbapi.unlockDevice = function(serial) { - return setLockOnDevice(serial, false) -} - -dbapi.unlockDevices = function(serials) { - return dbapi.setLockOnDevices(serials, false) -} - -dbapi.setLockOnDevices = function(serials, lock) { - return db.connect().then(client => { - return client.collection('devices').updateMany( - {serial: {$in: serials}} - , { - $set: { - 'group.lock': lock - } - } - ) - }) -} - -function setLockOnUser(email, state) { - return db.connect().then(client => { - return client.collection('users').findOne({email: email}).then(oldDoc => { - return client.collection('users').updateOne({ - email: email - } - , - { - $set: {'groups.lock': oldDoc.groups.lock !== state ? state : oldDoc.groups.lock} - } - ) - .then(updateStats => { - return client.collection('users').findOne({email: email}).then(newDoc => { - updateStats.changes = [ - {new_val: {...newDoc}, old_val: {...oldDoc}} - ] - return updateStats - }) - }) - }) - }) -} - -dbapi.lockUser = function(email) { - function wrappedlockUser() { - return setLockOnUser(email, true) - .then(function(stats) { - return apiutil.lockResult(stats) - }) - } - - return apiutil.setIntervalWrapper( - wrappedlockUser - , 10 - , Math.random() * 500 + 50) -} - -dbapi.unlockUser = function(email) { - return setLockOnUser(email, false) -} - -dbapi.lockGroupByOwner = function(email, id) { - function wrappedlockGroupByOwner() { - return dbapi.getRootGroup().then(function(rootGroup) { - return db.connect().then(client => { - return client.collection('users').findOne({email: email}).then(triggeredUser => { - return client.collection('groups').findOne({id: id}).then(group => { - if (!group) { - return { - modifiedCount: 0 - , matchedCount: 0 - } - } - return client.collection('groups').updateOne( - { - id: id - }, - { - $set: { - 'lock.user': !group.lock.admin && - !group.lock.user && - (group.owner.email === email || rootGroup.owner.email === email || triggeredUser.privilege === apiutil.ADMIN) ? - true : group.lock.user - } - } - ).then(updateStats => { - return client.collection('groups').findOne({id: id}).then(newDoc => { - updateStats.changes = [ - {new_val: {...newDoc}, old_val: {...group}} - ] - return updateStats - }) - }) - }) - }) - }) - .then(function(stats) { - const result = apiutil.lockResult(stats) - - if (!result.status) { - return dbapi.getGroupAsOwnerOrAdmin(email, id).then(function(group) { - if (!group) { - result.data.locked = false - result.status = true - } - return result - }) - } - return result - }) - }) - } - - return apiutil.setIntervalWrapper( - wrappedlockGroupByOwner - , 10 - , Math.random() * 500 + 50) -} - -dbapi.lockGroup = function(id) { - function wrappedlockGroup() { - return db.connect().then(client => { - return client.collection('groups').findOne({id: id}).then(group => { - return client.collection('groups').updateOne( - { - id: id - }, - { - $set: { - 'lock.user': !group.lock.admin && !group.lock.user ? true : group.lock.user - } - } - ).then(function(stats) { - return apiutil.lockResult(stats) - }) - }) - }) - } - - return apiutil.setIntervalWrapper( - wrappedlockGroup - , 10 - , Math.random() * 500 + 50) -} - -dbapi.unlockGroup = function(id) { - return db.connect().then(client => { - return client.collection('groups').updateMany( - {id: id}, - { - $set: { - 'lock.user': false - } - } - ) - }) -} - -dbapi.adminLockGroup = function(id, lock) { - function wrappedAdminLockGroup() { - return db.connect().then(client => { - return client.collection('groups').findOne({id: id}).then(oldDoc => { - return client.collection('groups').updateOne({ - id: id - }, - { - $set: { - 'lock.user': false - , 'lock.admin': true - } - } - ).then(updateStats => { - return client.collection('groups').findOne({id: id}).then(newDoc => { - updateStats.changes = [ - {new_val: {...newDoc}, old_val: {...oldDoc}} - ] - return updateStats - }) - }) - }) - .then(function(stats) { - const result = {} - - if (stats.modifiedCount > 0) { - result.status = - stats.changes[0].new_val.lock.admin && !stats.changes[0].old_val.lock.user - if (result.status) { - result.data = true - lock.group = stats.changes[0].new_val - } - } - else if (stats.skipped) { - result.status = true - } - return result - }) - }) - } - - return apiutil.setIntervalWrapper( - wrappedAdminLockGroup - , 10 - , Math.random() * 500 + 50) -} - -dbapi.adminUnlockGroup = function(lock) { - if (lock.group) { - return db.connect().then(client => { - return client.collection('groups').updateOne( - { - id: lock.group.id - } - , { - $set: { - 'lock.user': false - , 'lock.admin': false - } - } - ) - }) - } - return true -} - -dbapi.getRootGroup = function() { - return dbapi.getGroupByIndex(apiutil.ROOT, 'privilege').then(function(group) { - if (!group) { - throw new Error('Root group not found') - } - return group - }) -} - -dbapi.getUserGroup = function(email, id) { - return db.connect().then(client => { - return client.collection('groups').find({ - users: {$in: [email]} - , id: id - }).toArray().then(groups => { - return groups[0] - }) - }) -} - -dbapi.getUserGroups = function(email) { - return db.connect().then(client => { - return client.collection('groups').find({users: {$in: [email]}}).toArray() - }) -} - -dbapi.getOnlyUserGroups = function(email) { - return db.connect().then(client => { - return client.collection('groups').find({ - users: {$in: [email]} - , 'owner.email': {$ne: email} - }).toArray() - }) -} - -dbapi.getTransientGroups = function() { - return db.connect().then(client => { - return client.collection('groups').find({ - class: {$nin: [apiutil.BOOKABLE, apiutil.STANDARD]} - } - ).toArray() - }) -} - -dbapi.getDeviceTransientGroups = function(serial) { - return db.connect().then(client => { - return client.collection('groups').find({ - devices: serial - , class: {$nin: [apiutil.BOOKABLE, apiutil.STANDARD]} - } - ).toArray() - }) -} - -dbapi.isDeviceBooked = function(serial) { - return dbapi.getDeviceTransientGroups(serial) - .then(function(groups) { - return groups.length > 0 - }) -} - -dbapi.isRemoveGroupUserAllowed = function(email, targetGroup) { - if (targetGroup.class !== apiutil.BOOKABLE) { - return Promise.resolve(true) - } - return db.connect().then(client => { - return client.collection('groups').aggregate([ - {$match: {'owner.email': email}} - , { - $match: { - $and: [ - {$ne: ['$class', apiutil.BOOKABLE]} - , {$ne: ['$class', apiutil.STANDARD]} - , {$not: [{$eq: [{$setIntersection: [targetGroup.devices, ['$devices']]}, []]}]} - ] - } - } - ]).toArray() - }) - .then(function(groups) { - return groups.length === 0 - }) -} - -dbapi.isUpdateDeviceOriginGroupAllowed = function(serial, targetGroup) { - return dbapi.getDeviceTransientGroups(serial) - .then(function(groups) { - if (groups.length) { - if (targetGroup.class === apiutil.STANDARD) { - return false - } - for (const group of groups) { - if (targetGroup.users.indexOf(group.owner.email) < 0) { - return false - } - } - } - return true - }) -} - -dbapi.getDeviceGroups = function(serial) { - return db.connect().then(client => { - return client.collection('groups').find({ - devices: {$in: [serial]} - } - ).toArray() - }) -} - -dbapi.getGroupAsOwnerOrAdmin = function(email, id) { - return dbapi.getGroup(id).then(function(group) { - if (group) { - if (email === group.owner.email) { - return group - } - return dbapi.loadUser(email).then(function(user) { - if (user && user.privilege === apiutil.ADMIN) { - return group - } - return false - }) - } - return false - }) -} - -dbapi.getOwnerGroups = function(email) { - return dbapi.getRootGroup().then(function(group) { - if (email === group.owner.email) { - return dbapi.getGroups() - } - return dbapi.getGroupsByIndex(email, 'owner') - }) -} - -dbapi.createGroup = function(data) { - const id = util.format('%s', uuid.v4()).replace(/-/g, '') - return db.connect().then(client => { - let object = Object.assign(data, { - id: id - , users: _.union(data.users, [data.owner.email]) - , devices: [] - , createdAt: dbapi.getNow() - , lock: { - user: false - , admin: false - } - , ticket: null - , runUrl: data.runUrl - }) - return client.collection('groups').insertOne(object) - .then(() => { - return dbapi.getGroup(id) - }) - }) -} - -dbapi.createUserGroup = function(data) { - return dbapi.reserveUserGroupInstance(data.owner.email).then(function(stats) { - if (stats.modifiedCount > 0) { - return dbapi.getRootGroup().then(function(rootGroup) { - data.users = [rootGroup.owner.email] - return dbapi.createGroup(data).then(function(group) { - return Promise.all([ - dbapi.addGroupUser(group.id, group.owner.email) - , dbapi.addGroupUser(group.id, rootGroup.owner.email) - ]) - .then(function() { - return dbapi.getGroup(group.id) - }) - }) - }) - } - return false - }) -} - -dbapi.updateGroup = function(id, data) { - return db.connect().then(client => { - return client.collection('groups').updateOne( - {id: id} - , { - $set: data - } - ).then(() => { - return client.collection('groups').findOne({id: id}) - }) - }) -} - -dbapi.reserveUserGroupInstance = function(email) { - return db.connect().then(client => { - return client.collection('users').findOne({email: email}).then(user => { - let consumed, allocated - try { - consumed = user.groups.quotas.consumed.number - } - catch (TypeError) { - consumed = 0 - } - try { - allocated = user.groups.quotas.allocated.number - } - catch (TypeError) { - allocated = 0 - } - return client.collection('users').updateOne( - {email: email}, - { - $set: { - 'groups.quotas.consumed.number': consumed < allocated ? consumed + 1 : consumed - } - } - ) - }) - }) -} - -dbapi.releaseUserGroupInstance = function(email) { - return db.connect().then(client => { - return client.collection('users').findOne({email: email}).then(user => { - let consumed - try { - consumed = user.groups.quotas.consumed.number - } - catch (TypeError) { - consumed = 0 - } - return client.collection('users').updateOne( - {email: email}, - { - $set: { - 'groups.quotas.consumed.number': consumed >= 1 ? consumed - 1 : consumed - } - } - ) - }) - }) -} - -dbapi.updateUserGroupDuration = function(email, oldDuration, newDuration) { - return db.connect().then(client => { - return client.collection('users').updateOne( - {email: email} - , [{ - $set: { - 'groups.quotas.consumed.duration': { - $cond: [ - {$lte: [{$sum: ['$groups.quotas.consumed.duration', newDuration, -oldDuration]}, '$groups.quotas.allocated.duration']} - , {$sum: ['$groups.quotas.consumed.duration', newDuration, -oldDuration]} - , '$groups.quotas.consumed.duration' - ] - } - } - }] - ) - }) -} - -dbapi.updateUserGroupsQuotas = function(email, duration, number, repetitions) { - return db.connect().then(client => { - return client.collection('users').findOne({email: email}).then(oldDoc => { - let consumed = oldDoc.groups.quotas.consumed.duration - let allocated = oldDoc.groups.quotas.allocated.duration - let consumedNumber = oldDoc.groups.quotas.consumed.number - let allocatedNumber = oldDoc.groups.quotas.allocated.number - return client.collection('users').updateOne( - {email: email} - , { - $set: { - 'groups.quotas.allocated.duration': duration && consumed <= duration && - (!number || consumedNumber <= number) ? duration : allocated - , 'groups.quotas.allocated.number': number && consumedNumber <= number && - (!duration || consumed <= duration) ? number : allocatedNumber - , 'groups.quotas.repetitions': repetitions || oldDoc.groups.quotas.repetitions - } - } - ) - .then(updateStats => { - return client.collection('users').findOne({email: email}).then(newDoc => { - updateStats.changes = [ - {new_val: {...newDoc}, old_val: {...oldDoc}} - ] - return updateStats - }) - }) - }) - }) -} - -dbapi.updateDefaultUserGroupsQuotas = function(email, duration, number, repetitions) { - return db.connect().then(client => { - return client.collection('users').updateOne( - {email: email} - , [{ - $set: { - defaultGroupsDuration: { - $cond: [ - { - $ne: [duration, null] - } - , duration - , '$groups.quotas.defaultGroupsDuration' - ] - } - , defaultGroupsNumber: { - $cond: [ - { - $ne: [number, null] - } - , number - , '$groups.quotas.defaultGroupsNumber' - ] - } - , defaultGroupsRepetitions: { - $cond: [ - { - $ne: [repetitions, null] - } - , repetitions - , '$groups.quotas.defaultGroupsRepetitions' - ] - } - } - }] - ) - }) -} - -dbapi.updateDeviceGroupName = function(serial, group) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial} - , [{ - $set: { - 'group.name': { - $cond: [ - { - $eq: [apiutil.isOriginGroup(group.class), false] - } - , { - $cond: [ - { - $eq: [group.isActive, true] - } - , group.name - , '$group.name' - ] - } - , { - $cond: [ - { - $eq: ['$group.origin', '$group.id'] - } - , group.name - , '$group.name' - ] - } - ] - } - , 'group.originName': { - $cond: [ - { - $eq: [apiutil.isOriginGroup(group.class), true] - } - , group.name - , '$group.originName' - ] - } - } - }] - ) - }) -} - -dbapi.updateDeviceCurrentGroupFromOrigin = function(serial) { - return db.connect().then(client => { - return client.collection('devices').findOne({serial: serial}).then(device => { - return client.collection('groups').findOne({id: device.group.origin}).then(group => { - return client.collection('devices').updateOne( - {serial: serial} - , { - $set: { - 'group.id': device.group.origin - , 'group.name': device.group.originName - , 'group.owner': group.owner - , 'group.lifeTime': group.dates[0] - , 'group.class': group.class - , 'group.repetitions': group.repetitions - , 'group.runUrl': group.runUrl - } - } - ) - }) - }) - }) -} - -dbapi.askUpdateDeviceOriginGroup = function(serial, group, signature) { - return db.connect().then(client => { - return client.collection('groups').updateOne( - { - id: group.id - } - , { - $set: { - ticket: { - serial: serial - , signature: signature - } - } - } - ) - }) -} - -dbapi.updateDeviceOriginGroup = function(serial, group) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial} - , [{ - $set: { - 'group.origin': group.id - , 'group.originName': group.name - , 'group.id': { - $cond: [ - { - $eq: ['$group.id', '$group.origin'] - } - , group.id - , '$group.id' - ] - } - , 'group.name': { - $cond: [ - { - $eq: ['$group.id', '$group.origin'] - } - , group.name - , '$group.name' - ] - } - , 'group.owner': { - $cond: [ - { - $eq: ['$group.id', '$group.origin'] - } - , group.owner - , '$group.owner' - ] - } - , 'group.lifeTime': { - $cond: [ - { - $eq: ['$group.id', '$group.origin'] - } - , group.dates[0] - , '$group.lifeTime' - ] - } - , 'group.class': { - $cond: [ - { - $eq: ['$group.id', '$group.origin'] - } - , group.class - , '$group.class' - ] - } - , 'group.repetitions': { - $cond: [ - { - $eq: ['$group.id', '$group.origin'] - } - , group.repetitions - , '$group.repetitions' - ] - } - } - }] - ) - }) - .then(function() { - return db.connect().then(clients => { - return clients.collection('devices').findOne({serial: serial}) - }) - }) -} - -dbapi.updateDeviceCurrentGroup = function(serial, group) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: { - 'group.id': group.id - , 'group.name': group.name - , 'group.owner': group.owner - , 'group.lifeTime': group.dates[0] - , 'group.class': group.class - , 'group.repetitions': group.repetitions - } - } - ) - }) -} - -dbapi.updateDevicesCurrentGroup = function(serials, group) { - return db.connect().then(client => { - return client.collection('devices').updateMany( - {serial: {$in: serials}}, - { - $set: { - 'group.id': group.id - , 'group.name': group.name - , 'group.owner': group.owner - , 'group.lifeTime': group.dates[0] - , 'group.class': group.class - , 'group.repetitions': group.repetitions - , 'group.runUrl': group.runUrl - } - } - ) - }) -} - -dbapi.returnDeviceToOriginGroup = function(serial) { - return dbapi.loadDeviceBySerial(serial) - .then((device) => { - return dbapi.getRootGroup() - .then((group) => { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: device.serial}, - { - $set: { - 'group.id': group.id - , 'group.name': group.name - , 'group.owner': group.owner - , 'group.lifeTime': group.dates[0] - , 'group.class': group.class - , 'group.repetitions': group.repetitions - } - } - ) - }) - }) - }) -} - -dbapi.returnDevicesToOriginGroup = function(serials) { - return dbapi.getRootGroup() - .then(group => { - return db.connect().then(client => { - return client.collection('devices').updateMany( - {serial: {$in: serials}} - , { - $set: { - 'group.id': group.id - , 'group.name': group.name - , 'group.owner': group.owner - , 'group.lifeTime': group.dates[0] - , 'group.class': group.class - , 'group.repetitions': group.repetitions - } - } - ) - }) - }) -} - -dbapi.updateUserGroup = function(group, data) { - return dbapi.updateUserGroupDuration(group.owner.email, group.duration, data.duration) - .then(function(stats) { - if (stats.modifiedCount > 0 || stats.matchedCount > 0 && group.duration === data.duration) { - return dbapi.updateGroup(group.id, data) - } - return false - }) -} - -dbapi.deleteGroup = function(id) { - return db.connect().then(client => { - return client.collection('groups').deleteOne({id: id}) - }) -} - -dbapi.deleteUserGroup = function(id) { - function deleteUserGroup(group) { - return dbapi.deleteGroup(group.id) - .then(() => { - return Promise.map(group.users, function(email) { - return dbapi.removeGroupUser(group.id, email) - }) - }) - .then(() => { - return dbapi.releaseUserGroupInstance(group.owner.email) - }) - .then(() => { - return dbapi.updateUserGroupDuration(group.owner.email, group.duration, 0) - }) - .then(() => { - return dbapi.returnDevicesToOriginGroup(group.devices) - }) - .then(function() { - return 'deleted' - }) - } - - return dbapi.getGroup(id).then(function(group) { - if (group.privilege !== apiutil.ROOT) { - return deleteUserGroup(group) - } - return 'forbidden' - }) -} - -dbapi.createUser = function(email, name, ip) { - return dbapi.getRootGroup().then(function(group) { - return dbapi.loadUser(group.owner.email).then(function(adminUser) { - let userObj = { - email: email - , name: name - , ip: ip - , group: wireutil.makePrivateChannel() - , lastLoggedInAt: dbapi.getNow() - , createdAt: dbapi.getNow() - , forwards: [] - , settings: {} - , acceptedPolicy: false - , privilege: adminUser ? apiutil.USER : apiutil.ADMIN - , groups: { - subscribed: [] - , lock: false - , quotas: { - allocated: { - number: group.envUserGroupsNumber - , duration: group.envUserGroupsDuration - } - , consumed: { - number: 0 - , duration: 0 - } - , defaultGroupsNumber: group.envUserGroupsNumber - , defaultGroupsDuration: group.envUserGroupsDuration - , defaultGroupsRepetitions: group.envUserGroupsRepetitions - , repetitions: group.envUserGroupsRepetitions - } - } - } - return db.connect().then(client => { - return client.collection('users').insertOne(userObj) - }) - .then(function(stats) { - if (stats.insertedId) { - return dbapi.addGroupUser(group.id, email).then(function() { - return dbapi.loadUser(email).then(function(user) { - stats.changes = [ - {new_val: {...user}} - ] - return stats - }) - }) - } - return stats - }) - }) - }) -} - -dbapi.saveUserAfterLogin = function(user) { - return db.connect().then(client => { - return client.collection('users').updateOne({email: user.email}, - { - $set: { - name: user.name - , ip: user.ip - , lastLoggedInAt: dbapi.getNow() - } - }) - .then(function(stats) { - if (stats.modifiedCount === 0) { - return dbapi.createUser(user.email, user.name, user.ip) - } - return stats - }) - }) -} - -dbapi.loadUser = function(email) { - return db.connect().then(client => { - return client.collection('users').findOne({email: email}) - }) -} - -dbapi.updateUsersAlertMessage = function(alertMessage) { - return db.connect().then(client => { - return client.collection('users').updateOne( - { - email: apiutil.STF_ADMIN_EMAIL - } - , { - $set: Object.fromEntries(Object.entries(alertMessage).map(([key, value]) => - ['settings.alertMessage.' + key, value] - )), - } - ).then(updateStats => { - return client.collection('users').findOne({email: apiutil.STF_ADMIN_EMAIL}).then(updatedMainAdmin => { - updateStats.changes = [ - {new_val: {...updatedMainAdmin}} - ] - return updateStats - }) - }) - }) -} - -dbapi.updateUserSettings = function(email, changes) { - return db.connect().then(client => { - return client.collection('users').findOne({email: email}).then(user => { - return client.collection('users').updateOne( - { - email: email - } - , { - $set: { - settings: {...user.settings, ...changes} - } - } - ) - }) - }) -} - -dbapi.resetUserSettings = function(email) { - return db.connect().then(client => { - return client.collection('users').updateOne({email: email}, - { - $set: { - settings: {} - } - }) - }) -} - -dbapi.insertUserAdbKey = function(email, key) { - let data = { - title: key.title - , fingerprint: key.fingerprint - } - return db.connect().then(client => { - return client.collection('users').findOne({email: email}).then(user => { - let adbKeys = user.adbKeys ? user.adbKeys : [] - adbKeys.push(data) - return client.collection('users').updateOne( - {email: email} - , {$set: {adbKeys: user.adbKeys ? adbKeys : [data]}} - ) - }) - }) -} - -dbapi.deleteUserAdbKey = function(email, fingerprint) { - return db.connect().then(client => { - return client.collection('users').findOne({email: email}).then(user => { - return client.collection('users').updateOne( - {email: email} - , { - $set: { - adbKeys: user.adbKeys ? user.adbKeys.filter(key => { - return key.fingerprint !== fingerprint - }) : [] - } - } - ) - }) - }) -} - -dbapi.lookupUsersByAdbKey = function(fingerprint) { - return db.connect().then(client => { - return client.collection('users').find({ - adbKeys: fingerprint - }).toArray() - }) -} - -dbapi.lookupUserByAdbFingerprint = function(fingerprint) { - return db.connect().then(client => { - return client.collection('users').find( - {adbKeys: {$elemMatch: {fingerprint: fingerprint}}} - , {email: 1, name: 1, group: 1, _id: 0} - ).toArray() - .then(function(users) { - switch (users.length) { - case 1: - return users[0] - case 0: - return null - default: - throw new Error('Found multiple users for same ADB fingerprint') - } - }) - }) -} - -dbapi.lookupUserByVncAuthResponse = function(response, serial) { - return db.connect().then(client => { - return client.collection('vncauth').aggregate([ - { - $match: { - 'responsePerDevice.response': response - , 'responsePerDevice.serial': serial - } - } - , { - $lookup: { - from: 'users' - , localField: 'userId' - , foreignField: '_id' - , as: 'users' - } - } - , { - $project: { - email: 1 - , name: 1 - , group: 1 - } - } - ]).toArray() - }) - .then(function(groups) { - switch (groups.length) { - case 1: - return groups[0] - case 0: - return null - default: - throw new Error('Found multiple users with the same VNC response') - } - }) -} - -dbapi.loadUserDevices = function(email) { - return db.connect().then(client => { - return client.collection('users').findOne({email: email}).then(user => { - let userGroups = user.groups.subscribed - return client.collection('devices').find( - { - 'owner.email': email - , present: true - , 'group.id': {$in: userGroups} - } - ).toArray() - }) - }) -} - -dbapi.saveDeviceLog = function(serial, entry) { - return db.connect().then(client => { - return client.collection('logs').insertOne({ - id: uuid.v4() - , serial: serial - , timestamp: new Date(entry.timestamp) - , priority: entry.priority - , tag: entry.tag - , pid: entry.pid - , message: entry.message - } - ) - }) -} - -dbapi.saveDeviceInitialState = function(serial, device) { - let data = { - present: true - , presenceChangedAt: dbapi.getNow() - , provider: device.provider - , owner: null - , status: device.status - , statusChangedAt: dbapi.getNow() - , bookedBefore: 0 - , ready: false - , reverseForwards: [] - , remoteConnect: false - , remoteConnectUrl: null - , usage: null - , logs_enabled: false - } - return db.connect().then(client => { - return client.collection('devices').updateOne({serial: serial}, - { - $set: data - } - ) - .then(stats => { - if (stats.modifiedCount === 0 && stats.matchedCount === 0) { - return dbapi.getRootGroup().then(function(group) { - data.serial = serial - data.createdAt = dbapi.getNow() - data.group = { - id: group.id - , name: group.name - , lifeTime: group.dates[0] - , owner: group.owner - , origin: group.id - , class: group.class - , repetitions: group.repetitions - , originName: group.name - , lock: false - } - return client.collection('devices').insertOne(data) - .then(() => { - return dbapi.addOriginGroupDevice(group, serial) - }) - }) - } - return true - }) - .then(() => { - return client.collection('devices').findOne({serial: serial}) - }) - }) -} - -dbapi.setDeviceConnectUrl = function(serial, url) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: { - remoteConnectUrl: url - , remoteConnect: true - } - } - ) - }) -} - -dbapi.unsetDeviceConnectUrl = function(serial) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: { - remoteConnectUrl: null - , remoteConnect: false - } - } - ) - }) -} - -dbapi.saveDeviceStatus = function(serial, status) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: { - status: status - , statusChangedAt: dbapi.getNow() - } - } - ) - }) -} - -dbapi.enhanceStatusChangedAt = function(serial, timeout) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: { - statusChangedAt: dbapi.getNow() - , bookedBefore: timeout - } - } - ) - }) -} - -dbapi.setDeviceOwner = function(serial, owner) { - log.info('Setting device owner in db - ' + owner.email) - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: {owner: owner} - } - ) - }) -} - -dbapi.setDevicePlace = function(serial, place) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: {place: place} - } - ) - }) -} - -dbapi.setDeviceStorageId = function(serial, storageId) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: {storageId: storageId} - } - ) - }) -} - - -dbapi.unsetDeviceOwner = function(serial) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: {owner: null} - } - ) - }) -} - -dbapi.setDevicePresent = function(serial) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: { - present: true - , presenceChangedAt: dbapi.getNow() - } - } - ) - }) -} - -dbapi.setDeviceAbsent = function(serial) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: { - owner: null - , present: false - , presenceChangedAt: dbapi.getNow() - } - } - ) - }) -} - -dbapi.setDeviceUsage = function(serial, usage) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: { - usage: usage - , usageChangedAt: dbapi.getNow() - } - } - ) - }) -} - -dbapi.unsetDeviceUsage = function(serial) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: { - usage: null - , usageChangedAt: dbapi.getNow() - , logs_enabled: false - } - } - ) - }) -} - -dbapi.setDeviceAirplaneMode = function(serial, enabled) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: { - airplaneMode: enabled - } - } - ) - }) -} - -dbapi.setDeviceBattery = function(serial, battery) { - const batteryData = { - status: battery.status - , health: battery.health - , source: battery.source - , level: battery.level - , scale: battery.scale - , temp: battery.temp - , voltage: battery.voltage - } - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: {battery: batteryData} - } - ) - }) -} - -dbapi.setDeviceBrowser = function(serial, browser) { - const browserData = { - selected: browser.selected - , apps: browser.apps - } - - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: {browser: browserData} - } - ) - }) -} - -dbapi.setDeviceServicesAvailability = function(serial, service) { - const serviceData = { - hasHMS: service.hasHMS - , hasGMS: service.hasGMS - } - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: {service: serviceData} - } - ) - }) -} - -dbapi.setDeviceConnectivity = function(serial, connectivity) { - const networkData = { - connected: connectivity.connected - , type: connectivity.type - , subtype: connectivity.subtype - , failover: !!connectivity.failover - , roaming: !!connectivity.roaming - } - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: {network: networkData} - } - ) - }) -} - -dbapi.setDevicePhoneState = function(serial, state) { - const networkData = { - state: state.state - , manual: state.manual - , operator: state.operator - } - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: {network: networkData} - } - ) - }) -} - -dbapi.setDeviceRotation = function(serial, rotation) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: { - 'display.rotation': rotation - } - } - ) - }) -} - -dbapi.setIosDeviceRotation = function(message) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: message.serial}, - { - $set: { - 'display.rotation': message.rotation - , 'display.height': message.height - , 'display.width': message.width - } - } - ) - }) -} - -dbapi.setDeviceNote = function(serial, note) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: {notes: note} - } - ) - }) -} - -dbapi.setDeviceReverseForwards = function(serial, forwards) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: {reverseForwards: forwards} - } - ) - }) -} - -dbapi.setDeviceReady = function(serial, channel) { - const data = { - channel: channel - , ready: true - , owner: null - , present: true - , reverseForwards: [] - } - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: data - } - ) - }) -} - -dbapi.saveDeviceIdentity = function(serial, identity) { - const identityData = { - platform: identity.platform - , manufacturer: identity.manufacturer - , operator: identity.operator - , model: identity.model - , version: identity.version - , abi: identity.abi - , sdk: identity.sdk - , display: identity.display - , phone: identity.phone - , product: identity.product - , cpuPlatform: identity.cpuPlatform - , openGLESVersion: identity.openGLESVersion - , marketName: identity.marketName - , macAddress: identity.macAddress - , ram: identity.ram - } - - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: identityData - } - ) - }) -} - -dbapi.loadDevices = function(groups) { - return db.connect().then(client => { - if (groups.length > 0) { - return client.collection('devices').find( - { - 'group.id': {$in: groups} - } - ).toArray() - } - else { - return client.collection('devices').find().toArray() - } - }) -} - -dbapi.loadDevicesByOrigin = function(groups) { - return db.connect().then(client => { - return client.collection('devices').find( - {'group.origin': {$in: groups}} - ).toArray() - }) -} - -dbapi.loadBookableDevices = function(groups) { - return db.connect().then(client => { - return client.collection('devices').find( - { - $and: [ - {'group.origin': {$in: groups}} - , {present: {$eq: true}} - , {ready: {$eq: true}} - , {owner: {$eq: null}} - ] - } - ).toArray() - }) -} - -dbapi.loadBookableDevicesWithFiltersLock = function(groups, abi, model, type, sdk, version, devicesFunc, limit = null) { - let filterOptions = [] - let serials = [] - if (abi) { - filterOptions.push({abi: {$eq: abi}}) - } - if (model) { - filterOptions.push({model: {$eq: model}}) - } - if (sdk) { - filterOptions.push({sdk: {$eq: sdk}}) - } - if (version) { - filterOptions.push({version: {$eq: version}}) - } - if (type) { - filterOptions.push({type: {$eq: type}}) - } - return db.connect().then(client => { - let result = client.collection('devices').find( - { - $and: [ - {'group.origin': {$in: groups}} - , {'group.class': {$eq: apiutil.BOOKABLE}} - , {present: {$eq: true}} - , {ready: {$eq: true}} - , {owner: {$eq: null}} - , {'group.lock': {$eq: false}} - , ...filterOptions - ] - } - ) - if (limit) { - result = result.limit(limit) - } - return result.toArray() - .then(devices => { - serials = devices.map(device => device.serial) - dbapi.lockDevices(serials).then(() => { - return devicesFunc(devices) - }) - .finally(() => { - if (serials.length > 0) { - dbapi.unlockDevices(serials) - } - }) - }) - }) -} - -dbapi.loadStandardDevices = function(groups) { - return db.connect().then(client => { - return client.collection('devices') - .find({ - 'group.class': apiutil.STANDARD - , 'group.id': {$in: groups} - }) - .toArray() - }) -} - -dbapi.loadPresentDevices = function() { - return db.connect().then(client => { - return client.collection('devices').find({present: true}) - }) -} - -dbapi.loadDeviceBySerial = function(serial) { - return db.connect().then(client => { - return client.collection('devices').findOne({serial: serial}) - }) -} - -dbapi.loadDevicesBySerials = function(serials) { - return db.connect().then(client => { - return client.collection('devices').find({serial: {$in: serials}}).toArray() - }) -} - -dbapi.loadDevice = function(groups, serial) { - return db.connect().then(client => { - return client.collection('devices').findOne( - { - serial: serial - , 'group.id': {$in: groups} - } - ) - }) -} - -dbapi.loadBookableDevice = function(groups, serial) { - return db.connect().then(client => { - return client.collection('devices') - .find( - { - serial: serial - , 'group.origin': {$in: groups} - , 'group.class': {$ne: apiutil.STANDARD} - } - ) - .toArray() - }) -} - -dbapi.loadDeviceByCurrent = function(groups, serial) { - return db.connect().then(client => { - return client.collection('devices') - .find( - { - serial: serial - , 'group.id': {$in: groups} - } - ) - .toArray() - }) -} - -dbapi.loadDeviceByOrigin = function(groups, serial) { - return db.connect().then(client => { - return client.collection('devices') - .find( - { - serial: serial - , 'group.origin': {$in: groups} - } - ) - .toArray() - }) -} - -dbapi.saveUserAccessToken = function(email, token) { - return db.connect().then(client => { - return client.collection('accessTokens').insertOne( - { - email: email - , id: token.id - , title: token.title - , jwt: token.jwt - } - ) - }) -} - -dbapi.removeUserAccessTokens = function(email) { - return db.connect().then(client => { - return client.collection('accessTokens').deleteMany( - { - email: email - } - ) - }) -} - -dbapi.removeUserAccessToken = function(email, title) { - return db.connect().then(client => { - return client.collection('accessTokens').deleteOne( - { - email: email - , title: title - } - ) - }) -} - -dbapi.removeAccessToken = function(id) { - return db.connect().then(client => { - return client.collection('accessTokens').deleteOne({id: id}) - }) -} - -dbapi.loadAccessTokens = function(email) { - return db.connect().then(client => { - return client.collection('accessTokens').find({email: email}).toArray() - }) -} - -dbapi.loadAccessToken = function(id) { - return db.connect().then(client => { - return client.collection('accessTokens').findOne({id: id}) - }) -} - -dbapi.grantAdmin = function(email) { - return db.connect().then(client => { - return client.collection('users').updateOne({email: email}, - { - $set: { - privilege: apiutil.ADMIN - } - }) - }) -} - -dbapi.revokeAdmin = function(email) { - return db.connect().then(client => { - return client.collection('users').updateOne({email: email}, - { - $set: { - privilege: apiutil.USER - } - }) - }) -} - -dbapi.makeOriginGroupBookable = function() { - return db.connect().then(client => { - return client.collection('groups').updateOne( - { - name: 'Common' - } - , { - $set: { - class: apiutil.BOOKABLE - } - } - ) - }) -} - -dbapi.acceptPolicy = function(email) { - return db.connect().then(client => { - return client.collection('users').updateOne({email: email}, - { - $set: { - acceptedPolicy: true - } - }) - }) -} - -dbapi.writeStats = function(user, serial, action) { - return db.connect().then(client => { - return client.collection('stats').insertOne({ - id: (uuid.v4() + '_' + user.email + '_' + user.group) - , user: user.email - , action: action - , device: serial - , timestamp: dbapi.getNow() - } - ) - }) -} - -dbapi.getDevicesCount = function() { - return db.connect().then(client => { - return client.collection('devices').find().count() - }) -} - -dbapi.getOfflineDevicesCount = function() { - return db.connect().then(client => { - return client.collection('devices').find( - { - present: false - } - ).count() - }) -} - -dbapi.getOfflineDevices = function() { - return db.connect().then(client => { - return client.collection('devices').find( - {present: false}, - {_id: 0, 'provider.name': 1} - ).toArray() - }) -} - -dbapi.isPortExclusive = function(newPort) { - return dbapi.getAllocatedAdbPorts().then((ports) => { - let result = !!ports.find(port => port === newPort) - return !result - }) -} - -dbapi.getLastAdbPort = function() { - return dbapi.getAllocatedAdbPorts().then((ports) => { - if (ports.length === 0) { - return 0 - } - return Math.max(...ports) - }) -} - -dbapi.getAllocatedAdbPorts = function() { - return db.connect().then(client => { - return client.collection('devices').find({}, {adbPort: 1, _id: 0}).toArray().then(ports => { - let result = [] - ports.forEach((port) => { - if (port.adbPort) { - let portNum - if (typeof port.adbPort === 'string') { - portNum = parseInt(port.adbPort.replace('"', '').replace('\'', ''), 10) - } - else { - portNum = port.adbPort - } - result.push(portNum) - } - }) - return result.sort((a, b) => a - b) - }) - }) -} - -dbapi.initiallySetAdbPort = function(serial) { - return dbapi.getFreeAdbPort().then((port) => { - if (port) { - return dbapi.setAdbPort(serial, port) - } - else { - return null - } - }) -} - -dbapi.setAdbPort = function(serial, port) { - return db.connect().then(client => { - return client.collection('devices').updateOne({serial: serial}, {$set: {adbPort: port}}).then(() => { - return port - }) - }) -} - -dbapi.getAdbRange = function() { - return db.getRange() -} - -dbapi.getFreeAdbPort = function() { - const adbRange = dbapi.getAdbRange().split('-') - const adbRangeStart = parseInt(adbRange[0], 10) - const adbRangeEnd = parseInt(adbRange[1], 10) - - return dbapi.getLastAdbPort().then((lastPort) => { - if (lastPort === 0) { - return adbRangeStart - } - let freePort = lastPort + 1 - if (freePort > adbRangeEnd || freePort <= adbRangeStart) { - log.error('Port: ' + freePort + ' out of range [' + adbRangeStart + ':' + adbRangeEnd + ']') - return null - } - - return dbapi.isPortExclusive(freePort).then((result) => { - if (result) { - return freePort - } - else { - log.error('Port: ' + freePort + ' not exclusive.') - return null - } - }) - }) -} - -dbapi.generateIndexes = function() { - return db.connect().then(client => { - client.collection('devices').createIndex({serial: -1}, function(err, result) { - log.info('Created indexes with result - ' + result) - }) - }) -} - -dbapi.setDeviceSocketDisplay = function(data) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: data.serial}, - { - $set: { - iosClientChannel: data.channel - , 'display.density': 2 - , 'display.fps': 60 - , 'display.id': 0 - , 'display.rotation': 0 - , 'display.secure': true - , 'display.size': 4.971253395080566 - , 'display.xdpi': 294.9670104980469 - , 'display.ydpi': 295.56298828125 - , 'display.width': data.width - , 'display.height': data.height - } - } - ).then(() => { - dbapi.loadDeviceBySerial(data.serial) - }) - }) -} - -dbapi.setDeviceSocketPorts = function(data, publicIp) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: data.serial}, - { - $set: { - 'display.url': `ws://${publicIp}:${data.screenPort}/` - , 'display.screenPort': data.screenPort - , 'display.connectPort': data.connectPort - } - } - ).then(() => { - dbapi.loadDeviceBySerial(data.serial) - }) - }) -} - -dbapi.updateIosDevice = function(message) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - { - serial: message.id - }, - { - $set: { - id: message.id - , model: message.name - , platform: message.platform - , sdk: message.architect - } - } - ) - }) -} - -dbapi.setDeviceIosBattery = function(message) { - const batteryData = { - status: message.status - , health: message.health - , source: message.source - , level: message.level - , scale: message.scale - , temp: message.temp - } - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: message.id}, - { - $set: {battery: batteryData} - } - ) - }) -} - -dbapi.setDeviceIosVersion = function(message) { - const data = { - version: message.sdkVersion - } - - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: message.id}, - { - $set: data - } - ) - }) -} - -dbapi.sizeIosDevice = function(serial, height, width, scale) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: serial}, - { - $set: { - 'display.scale': scale - , 'display.height': height - , 'display.width': width - } - } - ) - }) -} - -dbapi.getDeviceDisplaySize = function(serial) { - return db.connect().then(client => { - return client.collection('devices').findOne({serial: serial}) - .then(result => { - return result.display - }) - }) -} - -dbapi.checkIosDeviceConnected = function(data) { - return dbapi.loadDeviceBySerial(data.id) -} - -dbapi.setAbsentDisconnectedDevices = function() { - return db.connect().then(client => { - return client.collection('devices').updateOne( - { - ios: true - }, - { - $set: { - present: false - , ready: false - } - } - ) - }) -} - -/* // @TODO refactor setDeviceApp -/!** - * - * @param message obj with application options - * @method check if exists app with equal option - * if exists replace it or append to installedApps list - * - *!/ -dbapi.setDeviceApp = function(message) { - return dbapi.loadDeviceBySerial(message.serial) - .then(result => { - let removePathApp = '' - let {installedApps} = result - let index = installedApps.findIndex(item => { - return ( - item.bundleName === message.bundleName && - item.bundleId === message.bundleId - ) - }) - if(index >= 0) { - removePathApp = installedApps[index].pathToApp - - installedApps[index] = { - bundleId: message.bundleId - , bundleName: message.bundleName - , pathToApp: message.pathToApp - } - db.run(r.table('devices').get(message.serial).update({ - installedApps - })) - return Promise.resolve({removePathApp}) - } - else { - db.run(r.table('devices').get(message.serial).update({ - installedApps: r.row('installedApps').default([]).append({ - bundleId: message.bundleId - , bundleName: message.bundleName - , pathToApp: message.pathToApp - }) - })) - return Promise.resolve({removePathApp: ''}) - } - }) - .catch(err => { - db.run(r.table('devices').get(message.serial).update({ - installedApps: r.row('installedApps').default([]).append({ - bundleId: message.bundleId - , bundleName: message.bundleName - , pathToApp: message.pathToApp - }) - })) - return Promise.reject(err) - }) -}*/ - -dbapi.getInstalledApplications = function(message) { - return dbapi.loadDeviceBySerial(message.serial) -} - -dbapi.getDeviceGroupOwner = function(serial) { - return db.connect().then(client => { - return client.collection('devices').findOne({serial: serial}) - .then(result => { - return result.group.owner - }) - }) -} - -dbapi.setDeviceGroupOwner = function(message) { - let data = { - 'group.owner.email': process.env.STF_ADMIN_EMAIL || 'administrator@fakedomain.com' - , 'group.owner.name': process.env.STF_ADMIN_NAME || 'administrator' - } - return db.connect().then(client => { - return client.collection('devices').updateOne( - {serial: message.serial}, - { - $set: data - } - ) - }) -} - -dbapi.setDeviceType = function(serial, type) { - return db.connect().then(client => { - return client.collection('devices').updateOne( - { - serial: serial - }, - { - $set: { - deviceType: type - } - } - ) - }) -} - -dbapi.getDeviceType = function(serial) { - return db.connect().then(client => { - return client.collection('devices').findOne({serial: serial}) - .then(result => { - return result.deviceType - }) - }) -} - -dbapi.initializeIosDeviceState = function(publicIp, message) { - const screenWsUrlPattern = - message.provider.screenWsUrlPattern || `ws://${publicIp}:${message.ports.screenPort}/` - - const data = { - present: true - , presenceChangedAt: dbapi.getNow() - , provider: message.provider - , owner: null - , status: message.status - , statusChangedAt: dbapi.getNow() - , ready: true - , reverseForwards: [] - , remoteConnect: false - , remoteConnectUrl: null - , usage: null - , ios: true - , iosClientChannel: message.serial - , display: { - density: DEFAULT_IOS_DEVICE_ARGS.DENSITY - , fps: DEFAULT_IOS_DEVICE_ARGS.FPS - , id: DEFAULT_IOS_DEVICE_ARGS.ID - , rotation: DEFAULT_IOS_DEVICE_ARGS.ROTATION - , secure: DEFAULT_IOS_DEVICE_ARGS.SECURE - , size: DEFAULT_IOS_DEVICE_ARGS.SIZE - , xdpi: DEFAULT_IOS_DEVICE_ARGS.XDPI - , ydpi: DEFAULT_IOS_DEVICE_ARGS.YDPI - , url: screenWsUrlPattern - } - , 'group.owner.email': process.env.STF_ADMIN_EMAIL || 'administrator@fakedomain.com' - , 'group.owner.name': process.env.STF_ADMIN_NAME || 'administrator' - , screenPort: message.ports.screenPort - , connectPort: message.ports.connectPort - , model: message.options.name - , platform: message.options.platform - , sdk: message.options.architect - , abi: 'arm64' - } - - return db.connect().then(client => { - return client.collection('devices').updateOne({serial: message.serial}, - { - $set: data - } - ) - .then(stats => { - if (stats.modifiedCount === 0 && stats.matchedCount === 0) { - return dbapi.getRootGroup().then(function(group) { - data.serial = message.serial - data.createdAt = dbapi.getNow() - data.group = { - id: group.id - , name: group.name - , lifeTime: group.dates[0] - , owner: group.owner - , origin: group.id - , class: group.class - , repetitions: group.repetitions - , originName: group.name - , lock: false - } - return client.collection('devices').insertOne(data) - .then(() => { - return dbapi.addOriginGroupDevice(group, message.serial) - }) - }) - } - return true - }) - .then(() => { - return client.collection('devices').findOne({serial: message.serial}) - }) - }) -} - -export default dbapi diff --git a/lib/db/index.js b/lib/db/index.js new file mode 100644 index 0000000000..2d0f895ae7 --- /dev/null +++ b/lib/db/index.js @@ -0,0 +1,82 @@ +import mongo from 'mongodb' + +import _setup from './setup.js' +import logger from '../util/logger.js' +import lifecycle from '../util/lifecycle.js' +import srv from '../util/srv.js' +import * as Sentry from '@sentry/node' +const log = logger.createLogger('db') + +let mongoClient +let options = { + // These environment variables are exposed when we --link to a + // MongoDB container. + url: process.env.MONGODB_PORT_27017_TCP || 'mongodb://127.0.0.1:27017' + , db: process.env.MONGODB_DB_NAME || 'stf' + , authKey: process.env.MONGODB_ENV_AUTHKEY + , adbPortsRange: process.env.adbPortsRange || '29000-29999' +} + +function _connect() { + return srv.resolve(options.url) + .then(records => { + let record = records.shift() + + if (!record) { + throw new Error('No hosts left to try') + } + const client = new mongo.MongoClient(options.url, {monitorCommands: true}) + // client.on('commandFailed', (event) => log.info('Command failed: ' + JSON.stringify(event))) + // client.on('commandSucceeded', (event) => log.info('Command succeded: ' + JSON.stringify(event))) + // client.on('commandStarted', (event) => log.info('Command started: ' + JSON.stringify(event))) + if (mongoClient) { + return mongoClient + } + else { + mongoClient = client.connect() + return mongoClient + } + }) +} + +export function connect() { + // return Sentry.startSpan({op: 'db', name: 'connect'}, () => { + return _connect() + .then(function(client) { + return client.db(options.db) + }) + .catch(function(err) { + log.fatal(err.message) + lifecycle.fatal() + }) + // }) +} + +// Verifies that we can form a connection. Useful if it's necessary to make +// sure that a handler doesn't run at all if the database is on a break. In +// normal operation connections are formed lazily. In particular, this was +// an issue with the processor unit, as it started processing messages before +// it was actually truly able to save anything to the database. This lead to +// lost messages in certain situations. +export function ensureConnectivity(fn) { + return function() { + let args = [].slice.call(arguments) + return connect().then(function() { + return fn.apply(null, args) + }) + } +} + + +// Sets up the database +export function setup() { + return connect().then(function(conn) { + return _setup(conn) + }) +} + +export function getRange() { + return '20000-29999' +} + +export * as default from './index.js' diff --git a/lib/db/index.mjs b/lib/db/index.mjs deleted file mode 100644 index 426356432a..0000000000 --- a/lib/db/index.mjs +++ /dev/null @@ -1,77 +0,0 @@ -import mongo from 'mongodb' - -import _setup from './setup.mjs' -import logger from '../util/logger.js' -import lifecycle from '../util/lifecycle.js' -import srv from '../util/srv.js' -const log = logger.createLogger('db') - -let mongoClient -let options = { - // These environment variables are exposed when we --link to a - // MongoDB container. - url: process.env.MONGODB_PORT_27017_TCP || 'mongodb://127.0.0.1:27017' - , db: process.env.MONGODB_DB_NAME || 'stf' - , authKey: process.env.MONGODB_ENV_AUTHKEY - , adbPortsRange: process.env.adbPortsRange || '29000-29999' -} - -function _connect() { - return srv.resolve(options.url) - .then(records => { - let record = records.shift() - - if (!record) { - throw new Error('No hosts left to try') - } - const client = new mongo.MongoClient(options.url, {monitorCommands: true}) - client.on('commandFailed', (event) => log.info('Command faled: ' + JSON.stringify(event))) - if (mongoClient) { - return mongoClient - } - else { - mongoClient = client.connect() - return mongoClient - } - }) -} - -export function connect() { - return _connect() - .then(function(client) { - return client.db(options.db) - }) - .catch(function(err) { - log.fatal(err.message) - lifecycle.fatal() - }) -} - -// Verifies that we can form a connection. Useful if it's necessary to make -// sure that a handler doesn't run at all if the database is on a break. In -// normal operation connections are formed lazily. In particular, this was -// an issue with the processor unit, as it started processing messages before -// it was actually truly able to save anything to the database. This lead to -// lost messages in certain situations. -export function ensureConnectivity(fn) { - return function() { - let args = [].slice.call(arguments) - return connect().then(function() { - return fn.apply(null, args) - }) - } -} - - -// Sets up the database -export function setup() { - return connect().then(function(conn) { - return _setup(conn) - }) -} - -export function getRange() { - return '20000-29999' -} - -export * as default from './index.mjs' diff --git a/lib/db/setup.js b/lib/db/setup.js new file mode 100755 index 0000000000..065df59fd0 --- /dev/null +++ b/lib/db/setup.js @@ -0,0 +1,45 @@ +import Promise from 'bluebird' +import logger from '../util/logger.js' +import tables from './tables.js' + +export default function(conn) { + let log = logger.createLogger('db:setup') + + function alreadyExistsError(err) { + return err.msg && err.msg.indexOf('already exists') !== -1 + } + + function noMasterAvailableError(err) { + return err.msg && err.msg.indexOf('No master available') !== -1 + } + + function createTable(table, options) { + let index = {} + index[options.primaryKey] = 1 + + return conn.createCollection(table, {changeStreamPreAndPostImages: {enabled: true}}) + .then(function() { + log.info('Table "%s" created', table) + return conn.collection(table).createIndex( + index + , { + unique: true + } + ) + }) + .catch(alreadyExistsError, function() { + log.info('Table "%s" already exists', table) + return Promise.resolve() + }) + .catch(noMasterAvailableError, function() { + return Promise.delay(1000).then(function() { + return createTable(table, options) + }) + }) + } + + + return Promise.all(Object.keys(tables).map(function(table) { + return createTable(table, tables[table]) + })).return(conn) +} diff --git a/lib/db/setup.mjs b/lib/db/setup.mjs deleted file mode 100644 index 1b82d7aaa0..0000000000 --- a/lib/db/setup.mjs +++ /dev/null @@ -1,45 +0,0 @@ -import Promise from 'bluebird' -import logger from '../util/logger.js' -import tables from './tables.mjs' - -export default function(conn) { - let log = logger.createLogger('db:setup') - - function alreadyExistsError(err) { - return err.msg && err.msg.indexOf('already exists') !== -1 - } - - function noMasterAvailableError(err) { - return err.msg && err.msg.indexOf('No master available') !== -1 - } - - function createTable(table, options) { - let index = {} - index[options.primaryKey] = 1 - - return conn.createCollection(table, {changeStreamPreAndPostImages: {enabled: true}}) - .then(function() { - log.info('Table "%s" created', table) - return conn.collection(table).createIndex( - index - , { - unique: true - } - ) - }) - .catch(alreadyExistsError, function() { - log.info('Table "%s" already exists', table) - return Promise.resolve() - }) - .catch(noMasterAvailableError, function() { - return Promise.delay(1000).then(function() { - return createTable(table, options) - }) - }) - } - - - return Promise.all(Object.keys(tables).map(function(table) { - return createTable(table, tables[table]) - })).return(conn) -} diff --git a/lib/db/tables.js b/lib/db/tables.js new file mode 100755 index 0000000000..11ee3f8b46 --- /dev/null +++ b/lib/db/tables.js @@ -0,0 +1,86 @@ +/** +* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0 +**/ + +import r from 'mongodb' + +export default { + users: { + primaryKey: 'email' + , indexes: { + adbKeys: { + indexFunction: function(user) { + return user('adbKeys')('fingerprint') + } + , options: { + multi: true + } + } + } + } + , accessTokens: { + primaryKey: 'id' + , indexes: { + email: null + } + } + , vncauth: { + primaryKey: 'password' + , indexes: { + response: null + , responsePerDevice: { + indexFunction: function(row) { + return [row('response'), row('deviceId')] + } + } + } + } + , devices: { + primaryKey: 'serial' + , indexes: { + owner: { + indexFunction: function(device) { + return r.branch( + device('present') + , device('owner')('email') + , r.literal() + ) + } + } + , logs_enabled: false + , present: null + , providerChannel: { + indexFunction: function(device) { + return device('provider')('channel') + } + } + , group: { + indexFunction: function(device) { + return device('group')('id') + } + } + } + } + , logs: { + primaryKey: 'id' + } + , groups: { + primaryKey: 'id' + , indexes: { + privilege: null + , owner: { + indexFunction: function(group) { + return group('owner')('email') + } + } + , startTime: { + indexFunction: function(group) { + return group('dates').nth(0)('start') + } + } + } + } + , stats: { + primaryKey: 'id' + } +} diff --git a/lib/db/tables.mjs b/lib/db/tables.mjs deleted file mode 100644 index f66ee2c0b4..0000000000 --- a/lib/db/tables.mjs +++ /dev/null @@ -1,86 +0,0 @@ -/** -* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0 -**/ - -import r from 'mongodb' - -export default { - users: { - primaryKey: 'email' - , indexes: { - adbKeys: { - indexFunction: function(user) { - return user('adbKeys')('fingerprint') - } - , options: { - multi: true - } - } - } - } -, accessTokens: { - primaryKey: 'id' - , indexes: { - email: null - } - } -, vncauth: { - primaryKey: 'password' - , indexes: { - response: null - , responsePerDevice: { - indexFunction: function(row) { - return [row('response'), row('deviceId')] - } - } - } - } -, devices: { - primaryKey: 'serial' - , indexes: { - owner: { - indexFunction: function(device) { - return r.branch( - device('present') - , device('owner')('email') - , r.literal() - ) - } - } - , logs_enabled: false - , present: null - , providerChannel: { - indexFunction: function(device) { - return device('provider')('channel') - } - } - , group: { - indexFunction: function(device) { - return device('group')('id') - } - } - } - } -, logs: { - primaryKey: 'id' - } -, groups: { - primaryKey: 'id' - , indexes: { - privilege: null - , owner: { - indexFunction: function(group) { - return group('owner')('email') - } - } - , startTime: { - indexFunction: function(group) { - return group('dates').nth(0)('start') - } - } - } - } -, stats: { - primaryKey: 'id' - } -} diff --git a/lib/units/api/controllers/autotests.js b/lib/units/api/controllers/autotests.js index 1f63a70d0a..e2c27b2b60 100644 --- a/lib/units/api/controllers/autotests.js +++ b/lib/units/api/controllers/autotests.js @@ -1,6 +1,6 @@ -import apiutil from '../../../util/apiutil.js' +import * as apiutil from '../../../util/apiutil.js' import groups from './groups.js' -import dbapi from '../../../db/api.mjs' +import * as dbapi from '../../../db/api.js' import datautil from '../../../util/datautil.js' import deviceutil from '../../../util/deviceutil.js' import wireutil from '../../../wire/util.js' @@ -8,6 +8,7 @@ import wirerouter from '../../../wire/router.js' import wire from '../../../wire/index.js' import uuid from 'uuid' import logger from '../../../util/logger.js' +import * as Sentry from '@sentry/node' import request from 'postman-request' import _ from 'lodash' let log = logger.createLogger('api:controllers:autotests') @@ -55,35 +56,35 @@ function captureDevices(req, res) { log.info('Timeout - ' + timeout) groups.createGroupFunc(res, apiutil.ONCE, email, 0, runId, username, privilege, false, dates, start, stop, 0, state, runUrl) .then(function(group) { - if (group) { - const deviceReq = { // fucking hell - params: { - id: group.id - } - , query: {} - , body: { - amount: amount - , needAmount: needAmount - , isInternal: true - , abi: abi - , model: model - , version: version - , sdk: sdk - , type: type + if (group) { + const deviceReq = { // fucking hell + params: { + id: group.id + } + , query: {} + , body: { + amount: amount + , needAmount: needAmount + , isInternal: true + , abi: abi + , model: model + , version: version + , sdk: sdk + , type: type + } + , user: req.user, } - , user: req.user, + return dbapi.addAdminsToGroup(group.id).then(() => { + return groups.addGroupDevices(deviceReq, res) + }) } - return dbapi.addAdminsToGroup(group.id).then(() => { - return groups.addGroupDevices(deviceReq, res) - }) - } - else { - apiutil.respond(res, 403, 'Forbidden (groups number quota is reached)') - } - }) + else { + apiutil.respond(res, 403, 'Forbidden (groups number quota is reached, autotests)') + } + }) .catch(function(err) { - apiutil.internalError(res, 'Failed to create group: ', err.stack) - }) + apiutil.internalError(res, 'Failed to create group: ', err.stack) + }) } function freeDevices(req, res) { const groupId = req.query.group @@ -127,25 +128,25 @@ function installOnDevice(req, res) { }, apiutil.INSTALL_APK_WAIT) let messageListener = wirerouter() .on(wire.InstallResultMessage, function(channel, message) { - if (message.serial === serial) { - clearTimeout(timer) - req.options.sub.unsubscribe(responseChannel) - req.options.channelRouter.removeListener(responseChannel, messageListener) - log.info('Installation result:' + message.result) - if (message.result === 'Installed successfully') { - return res.json({ - success: true - , description: message.result - }) - } - else { - return res.status(400).json({ - success: false - , description: message.result - }) + if (message.serial === serial) { + clearTimeout(timer) + req.options.sub.unsubscribe(responseChannel) + req.options.channelRouter.removeListener(responseChannel, messageListener) + log.info('Installation result:' + message.result) + if (message.result === 'Installed successfully') { + return res.json({ + success: true + , description: message.result + }) + } + else { + return res.status(400).json({ + success: false + , description: message.result + }) + } } - } - }) + }) .handler() req.options.channelRouter.on(responseChannel, messageListener) let isApi = true @@ -161,87 +162,98 @@ function useAndConnectDevice(req, res) { let serial = req.hasOwnProperty('body') ? req.body.serial : req.query.serial dbapi.loadDevice(req.user.groups.subscribed, serial) .then(function(device) { - let timeout = new Date(device.group.lifeTime.stop) - Date.now() - log.info('autotests use for device ' + device.serial + ' in group ' + device.group + ' with timeout ' + timeout) - if (!device) { - return res.status(404).json({ - success: false - , description: 'Device not found' - }) - } - datautil.normalize(device, req.user) - if (!deviceutil.isAddable(device, req.user)) { - return res.status(403).json({ - success: false - , description: 'Device is currently in use or not available' - }) - } - // Timer will be called if no JoinGroupMessage is received till 5 seconds - let responseTimer = setTimeout(function() { - req.options.channelRouter.removeListener(wireutil.global, useDeviceMessageListener) - return res.status(504).json({ - success: false - , description: 'Device is not responding' - }) - }, apiutil.GRPC_WAIT_TIMEOUT) - let useDeviceMessageListener = wirerouter() - .on(wire.JoinGroupMessage, function(channel, message) { - log.info(device.serial + ' added to user group ' + req.user) - if (message.serial === serial && message.owner.email === req.user.email) { - clearTimeout(responseTimer) + let timeout = new Date(device.group.lifeTime.stop) - Date.now() + log.info('autotests use for device ' + device.serial + ' in group ' + device.group + ' with timeout ' + timeout) + if (!device) { + return res.status(404).json({ + success: false + , description: 'Device not found' + }) + } + datautil.normalize(device, req.user) + if (!deviceutil.isAddable(device, req.user)) { + return res.status(403).json({ + success: false + , description: 'Device is currently in use or not available' + }) + } + // Timer will be called if no JoinGroupMessage is received till 5 seconds + let responseTimer = setTimeout(function() { req.options.channelRouter.removeListener(wireutil.global, useDeviceMessageListener) - let responseChannel = 'txn_' + uuid.v4() - req.options.sub.subscribe(responseChannel) - // Timer will be called if no JoinGroupMessage is received till 5 seconds - let timer = setTimeout(function() { - req.options.channelRouter.removeListener(responseChannel, useDeviceMessageListener) - req.options.sub.unsubscribe(responseChannel) - return res.status(504).json({ - success: false - , description: 'Device is not responding' - }) - }, apiutil.GRPC_WAIT_TIMEOUT) - let messageListener = wirerouter() - .on(wire.ConnectStartedMessage, function(channel, message) { - if (message.serial === serial) { - clearTimeout(timer) - req.options.sub.unsubscribe(responseChannel) - req.options.channelRouter.removeListener(responseChannel, messageListener) - return res.json({ - success: true - , description: 'Device is in use and remote connection is enabled' - , remoteConnectUrl: message.url - }) + Sentry.captureMessage('504: Device is not responding (failed to join group)') + return res.status(504).json({ + success: false + , description: 'Device is not responding (failed to join group)' + }) + }, apiutil.GRPC_WAIT_TIMEOUT) + let useDeviceMessageListener = wirerouter() + .on(wire.JoinGroupMessage, function(channel, message) { + log.info(device.serial + ' added to user group ' + req.user) + if (message.serial === serial && message.owner.email === req.user.email) { + clearTimeout(responseTimer) + req.options.channelRouter.removeListener(wireutil.global, useDeviceMessageListener) + let responseChannel = 'txn_' + uuid.v4() + req.options.sub.subscribe(responseChannel) + // Timer will be called if no JoinGroupMessage is received till 5 seconds + let timer = setTimeout(function() { + req.options.channelRouter.removeListener(responseChannel, useDeviceMessageListener) + req.options.sub.unsubscribe(responseChannel) + Sentry.captureMessage('504: Device is not responding (failed to connect to device)') + return res.status(504).json({ + success: false + , description: 'Device is not responding (failed to connect to device)' + }) + }, apiutil.GRPC_WAIT_TIMEOUT) + let messageListener = wirerouter() + .on(wire.ConnectStartedMessage, function(channel, message) { + if (message.serial === serial) { + clearTimeout(timer) + req.options.sub.unsubscribe(responseChannel) + req.options.channelRouter.removeListener(responseChannel, messageListener) + return res.json({ + success: true + , description: 'Device is in use and remote connection is enabled' + , remoteConnectUrl: message.url + }) + } + }) + .handler() + req.options.channelRouter.on(responseChannel, messageListener) + req.options.push.send([ + device.channel + , wireutil.transaction(responseChannel, new wire.ConnectStartMessage()) + ]) } }) - .handler() - req.options.channelRouter.on(responseChannel, messageListener) - req.options.push.send([ - device.channel - , wireutil.transaction(responseChannel, new wire.ConnectStartMessage()) - ]) - } + .handler() + req.options.channelRouter.on(wireutil.global, useDeviceMessageListener) + let usage = 'automation' + req.options.push.send([ + device.channel + , wireutil.envelope(new wire.UngroupMessage(wireutil.toDeviceRequirements({ + serial: { + value: serial + , match: 'exact' + } + }))) + ]) + req.options.push.send([ + device.channel + , wireutil.envelope(new wire.GroupMessage(new wire.OwnerMessage(req.user.email, req.user.name, req.user.group), timeout, wireutil.toDeviceRequirements({ + serial: { + value: serial + , match: 'exact' + } + }), usage)) + ]) }) - .handler() - req.options.channelRouter.on(wireutil.global, useDeviceMessageListener) - let usage = 'automation' - req.options.push.send([ - device.channel - , wireutil.envelope(new wire.GroupMessage(new wire.OwnerMessage(req.user.email, req.user.name, req.user.group), timeout, wireutil.toDeviceRequirements({ - serial: { - value: serial - , match: 'exact' - } - }), usage)) - ]) - }) .catch(function(err) { - log.error('Failed to load device "%s": ', req.params.serial, err.stack) - res.status(500).json({ - success: false - , description: 'Internal Server Error' + log.error('Failed to load device "%s": ', req.params.serial, err.stack) + res.status(500).json({ + success: false + , description: 'Internal Server Error' + }) }) - }) } export {captureDevices} export {freeDevices} diff --git a/lib/units/api/controllers/devices.js b/lib/units/api/controllers/devices.js index 4c0e717d08..66e7f402ad 100644 --- a/lib/units/api/controllers/devices.js +++ b/lib/units/api/controllers/devices.js @@ -1,9 +1,9 @@ import _ from 'lodash' import Promise from 'bluebird' -import dbapi from '../../../db/api.mjs' +import * as dbapi from '../../../db/api.js' import logger from '../../../util/logger.js' -import apiutil from '../../../util/apiutil.js' -import lockutil from '../../../util/lockutil.js' +import * as apiutil from '../../../util/apiutil.js' +import * as lockutil from '../../../util/lockutil.js' import util from 'util' import datautil from '../../../util/datautil.js' import uuid from 'uuid' @@ -46,16 +46,16 @@ function getGenericDevices(req, res, loadDevices) { if (!adbPort) { dbapi.initiallySetAdbPort(device.serial) .then(port => { - if (port) { - log.info('Applied adb port ' + port + ' for ' + device.serial) - device.adbPort = port - } - else { - log.warn('Cant apply port for ' + device.serial) - } - count++ - cb(devices[count]) - }) + if (port) { + log.info('Applied adb port ' + port + ' for ' + device.serial) + device.adbPort = port + } + else { + log.warn('Cant apply port for ' + device.serial) + } + count++ + cb(devices[count]) + }) } else { count++ @@ -69,8 +69,8 @@ function getGenericDevices(req, res, loadDevices) { } }) .catch(function(err) { - apiutil.internalError(res, 'Failed to load device list: ', err.stack) - }) + apiutil.internalError(res, 'Failed to load device list: ', err.stack) + }) } function getDeviceFilteredGroups(serial, fields, bookingOnly) { return dbapi.getDeviceGroups(serial).then(function(groups) { @@ -80,13 +80,13 @@ function getDeviceFilteredGroups(serial, fields, bookingOnly) { 'filtered' }) .then(function(groups) { - return _.without(groups, 'filtered').map(function(group) { - if (fields) { - return _.pick(apiutil.publishGroup(group), fields.split(',')) - } - return apiutil.publishGroup(group) + return _.without(groups, 'filtered').map(function(group) { + if (fields) { + return _.pick(apiutil.publishGroup(group), fields.split(',')) + } + return apiutil.publishGroup(group) + }) }) - }) }) } function extractStandardizableDevices(devices) { @@ -99,18 +99,18 @@ function extractStandardizableDevices(devices) { return true }) .then(function() { - return device - }) + return device + }) .catch(function(err) { - if (err !== 'booked') { - throw err - } - return err - }) + if (err !== 'booked') { + throw err + } + return err + }) }) .then(function(devices) { - return _.without(devices, 'booked') - }) + return _.without(devices, 'booked') + }) }) } function getStandardizableDevices(req, res) { @@ -120,8 +120,8 @@ function getStandardizableDevices(req, res) { }) }) .catch(function(err) { - apiutil.internalError(res, 'Failed to load device list: ', err.stack) - }) + apiutil.internalError(res, 'Failed to load device list: ', err.stack) + }) } function removeDevice(serial, req, res) { const presentState = req.query.present @@ -148,8 +148,8 @@ function removeDevice(serial, req, res) { return group }) .finally(function() { - lockutil.unlockGroup(lock) - }) + lockutil.unlockGroup(lock) + }) } function deleteDeviceInDatabase() { function wrappedDeleteDeviceInDatabase() { @@ -161,20 +161,20 @@ function removeDevice(serial, req, res) { if (device && device.group.id === device.group.origin) { return deleteGroupDevice(device.group.owner.email, device.group.id) .then(function(group) { - if (group !== 'not found') { - return dbapi.deleteDevice(serial).then(function() { - result.status = true - result.data = 'deleted' - }) - } - return false - }) + if (group !== 'not found') { + return dbapi.deleteDevice(serial).then(function() { + result.status = true + result.data = 'deleted' + }) + } + return false + }) } return false }) .then(function() { - return result - }) + return result + }) } return apiutil.setIntervalWrapper(wrappedDeleteDeviceInDatabase, 10, Math.random() * 500 + 50) } @@ -202,36 +202,36 @@ function removeDevice(serial, req, res) { return deleteGroupDevice(group.owner.email, group.id) }) .then(function() { - if (!groups.length && !anyBookingState && bookingState) { - return 'unchanged' - } - return deleteDeviceInDatabase() - }) + if (!groups.length && !anyBookingState && bookingState) { + return 'unchanged' + } + return deleteDeviceInDatabase() + }) }) }) .finally(function() { - lockutil.unlockDevice(lock) - }) + lockutil.unlockDevice(lock) + }) } /* ------------------------------------ PUBLIC FUNCTIONS ------------------------------- */ function getDevices(req, res) { const target = req.query.target switch (target) { - case apiutil.BOOKABLE: - getGenericDevices(req, res, dbapi.loadBookableDevices) - break - case apiutil.ORIGIN: - getGenericDevices(req, res, dbapi.loadDevicesByOrigin) - break - case apiutil.STANDARD: - getGenericDevices(req, res, dbapi.loadStandardDevices) - break - case apiutil.STANDARDIZABLE: - getStandardizableDevices(req, res) - break - default: - getGenericDevices(req, res, dbapi.loadDevices) + case apiutil.BOOKABLE: + getGenericDevices(req, res, dbapi.loadBookableDevices) + break + case apiutil.ORIGIN: + getGenericDevices(req, res, dbapi.loadDevicesByOrigin) + break + case apiutil.STANDARD: + getGenericDevices(req, res, dbapi.loadStandardDevices) + break + case apiutil.STANDARDIZABLE: + getStandardizableDevices(req, res) + break + default: + getGenericDevices(req, res, dbapi.loadDevices) } } function getDeviceBySerial(req, res) { @@ -239,38 +239,38 @@ function getDeviceBySerial(req, res) { var fields = req.query.fields dbapi.loadDevice(req.user.groups.subscribed, serial) .then(device => { - if (!device) { - return res.status(404).json({ - success: false - , description: 'Device not found' + if (!device) { + return res.status(404).json({ + success: false + , description: 'Device not found' + }) + } + let responseDevice = apiutil.publishDevice(device, req) + if (fields) { + responseDevice = _.pick(device, fields.split(',')) + } + res.json({ + success: true + , device: responseDevice }) - } - let responseDevice = apiutil.publishDevice(device, req) - if (fields) { - responseDevice = _.pick(device, fields.split(',')) - } - res.json({ - success: true - , device: responseDevice }) - }) .catch(function(err) { - log.error('Failed to load device "%s": ', serial, err.stack) - res.status(500).json({ - success: false + log.error('Failed to load device "%s": ', serial, err.stack) + res.status(500).json({ + success: false + }) }) - }) } function getDeviceSize(req, res) { var serial = req.params.serial dbapi.getDeviceDisplaySize(serial) .then(response => { - return res.status(200).json(response.display) - }) + return res.status(200).json(response.display) + }) .catch(function(err) { - log.info('Failed to get device size: ', err.stack) - return res.status(200).json({height: 0, width: 0, scale: 0}) - }) + log.info('Failed to get device size: ', err.stack) + return res.status(200).json({height: 0, width: 0, scale: 0}) + }) } function getDeviceGroups(req, res) { const serial = req.params.serial @@ -279,49 +279,49 @@ function getDeviceGroups(req, res) { return groups }) .then(function(devices) { - if (!devices.length) { - apiutil.respond(res, 404, 'Not Found (device)') - } - else { - getDeviceFilteredGroups(serial, fields, false) - .then(function(groups) { - return apiutil.respond(res, 200, 'Groups Information', {groups: groups}) - }) - } - }) + if (!devices.length) { + apiutil.respond(res, 404, 'Not Found (device)') + } + else { + getDeviceFilteredGroups(serial, fields, false) + .then(function(groups) { + return apiutil.respond(res, 200, 'Groups Information', {groups: groups}) + }) + } + }) .catch(function(err) { - apiutil.internalError(res, 'Failed to get device groups: ', err.stack) - }) + apiutil.internalError(res, 'Failed to get device groups: ', err.stack) + }) } function getDeviceOwner(req, res) { var serial = req.params.serial dbapi.getDeviceGroupOwner(serial) .then(response => { - if (!response) { - return res.status(404).json({ + if (!response) { + return res.status(404).json({ + success: false + , description: 'Device not found' + }) + } + return res.status(200).json(response) + }) + .catch(function(err) { + log.info('Failed to get device owner: ', err.stack) + res.status(500).json({ success: false - , description: 'Device not found' }) - } - return res.status(200).json(response) - }) - .catch(function(err) { - log.info('Failed to get device owner: ', err.stack) - res.status(500).json({ - success: false }) - }) } function getDeviceType(req, res) { var serial = req.params.serial dbapi.getDeviceType(serial) .then(response => { - return res.status(200).json(response) - }) + return res.status(200).json(response) + }) .catch(function(err) { - log.info('Failed to get device type: ', err.stack) - return res.status(200).json({deviceType: null}) - }) + log.info('Failed to get device type: ', err.stack) + return res.status(200).json({deviceType: null}) + }) } function getDeviceBookings(req, res) { const serial = req.params.serial @@ -330,19 +330,19 @@ function getDeviceBookings(req, res) { return groups }) .then(function(devices) { - if (!devices.length) { - apiutil.respond(res, 404, 'Not Found (device)') - } - else { - getDeviceFilteredGroups(serial, fields, true) - .then(function(bookings) { - apiutil.respond(res, 200, 'Bookings Information', {bookings: bookings}) - }) - } - }) + if (!devices.length) { + apiutil.respond(res, 404, 'Not Found (device)') + } + else { + getDeviceFilteredGroups(serial, fields, true) + .then(function(bookings) { + apiutil.respond(res, 200, 'Bookings Information', {bookings: bookings}) + }) + } + }) .catch(function(err) { - apiutil.internalError(res, 'Failed to get device bookings: ', err.stack) - }) + apiutil.internalError(res, 'Failed to get device bookings: ', err.stack) + }) } function addOriginGroupDevices(req, res) { const serials = apiutil.getBodyParameter(req.body, 'serials') @@ -360,19 +360,19 @@ function addOriginGroupDevices(req, res) { }, apiutil.GRPC_WAIT_TIMEOUT) messageListener = wirerouter() .on(wire.DeviceOriginGroupMessage, function(channel, message) { - if (message.signature === signature) { - clearTimeout(responseTimer) - req.options.channelRouter.removeListener(wireutil.global, messageListener) - dbapi.loadDeviceBySerial(serial).then(function(device) { - if (fields) { - resolve(_.pick(apiutil.publishDevice(device, req), fields.split(','))) - } - else { - resolve(apiutil.publishDevice(device, req)) - } - }) - } - }) + if (message.signature === signature) { + clearTimeout(responseTimer) + req.options.channelRouter.removeListener(wireutil.global, messageListener) + dbapi.loadDeviceBySerial(serial).then(function(device) { + if (fields) { + resolve(_.pick(apiutil.publishDevice(device, req), fields.split(','))) + } + else { + resolve(apiutil.publishDevice(device, req)) + } + }) + } + }) .handler() req.options.channelRouter.on(wireutil.global, messageListener) return dbapi.askUpdateDeviceOriginGroup(serial, group, signature) @@ -387,16 +387,16 @@ function addOriginGroupDevices(req, res) { lock.device = stats.changes[0].new_val return dbapi.isUpdateDeviceOriginGroupAllowed(serial, group) .then(function(updatingAllowed) { - if (!updatingAllowed) { - apiutil.respond(res, 403, 'Forbidden (device is currently booked)') - return Promise.reject('booked') - } - return askUpdateDeviceOriginGroup(group, serial) - }) + if (!updatingAllowed) { + apiutil.respond(res, 403, 'Forbidden (device is currently booked)') + return Promise.reject('booked') + } + return askUpdateDeviceOriginGroup(group, serial) + }) }) .finally(function() { - lockutil.unlockDevice(lock) - }) + lockutil.unlockDevice(lock) + }) } function updateDevicesOriginGroup(group, serials) { let results = [] @@ -406,28 +406,28 @@ function addOriginGroupDevices(req, res) { }) }) .then(function() { - const result = target === 'device' ? {device: {}} : {devices: []} - results = _.without(results, 'unchanged') - if (!results.length) { - return apiutil.respond(res, 200, `Unchanged (${target})`, result) - } - results = _.without(results, 'not found') - if (!results.length) { - return apiutil.respond(res, 404, `Not Found (${target})`) - } - if (target === 'device') { - result.device = results[0] - } - else { - result.devices = results - } - return apiutil.respond(res, 200, `Updated (${target})`, result) - }) + const result = target === 'device' ? {device: {}} : {devices: []} + results = _.without(results, 'unchanged') + if (!results.length) { + return apiutil.respond(res, 200, `Unchanged (${target})`, result) + } + results = _.without(results, 'not found') + if (!results.length) { + return apiutil.respond(res, 404, `Not Found (${target})`) + } + if (target === 'device') { + result.device = results[0] + } + else { + result.devices = results + } + return apiutil.respond(res, 200, `Updated (${target})`, result) + }) .catch(function(err) { - if (err !== 'booked' && err !== 'timeout' && err !== 'busy') { - throw err - } - }) + if (err !== 'booked' && err !== 'timeout' && err !== 'busy') { + throw err + } + }) } return lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) { if (lockingSuccessed) { @@ -447,23 +447,23 @@ function addOriginGroupDevices(req, res) { return extractStandardizableDevices(devices) }) .then(function(devices) { - const serials = [] - devices.forEach(function(device) { - if (group.devices.indexOf(device.serial) < 0) { - serials.push(device.serial) - } + const serials = [] + devices.forEach(function(device) { + if (group.devices.indexOf(device.serial) < 0) { + serials.push(device.serial) + } + }) + return updateDevicesOriginGroup(group, serials) }) - return updateDevicesOriginGroup(group, serials) - }) } return false }) .catch(function(err) { - apiutil.internalError(res, `Failed to update ${target} origin group: `, err.stack) - }) + apiutil.internalError(res, `Failed to update ${target} origin group: `, err.stack) + }) .finally(function() { - lockutil.unlockGroup(lock) - }) + lockutil.unlockGroup(lock) + }) } function addOriginGroupDevice(req, res) { apiutil.redirectApiWrapper('serial', addOriginGroupDevices, req, res) @@ -484,8 +484,8 @@ function removeOriginGroupDevices(req, res) { return false }) .finally(function() { - lockutil.unlockGroup(lock) - }) + lockutil.unlockGroup(lock) + }) } function removeOriginGroupDevice(req, res) { apiutil.redirectApiWrapper('serial', removeOriginGroupDevices, req, res) @@ -495,16 +495,16 @@ function putDeviceInfoBySerial(req, res) { const body = req.body dbapi.loadDeviceBySerial(serial) .then((data) => { - if (!data) { - return apiutil.respond(res, 404, `Not Found (${serial})`) - } - var updates = [] - // Update fields based on given body - if (_.has(body, 'note')) { - updates.push(dbapi.setDeviceNote(serial, body.note)) - } - if (_.has(body, 'status')) { - switch (body.status) { + if (!data) { + return apiutil.respond(res, 404, `Not Found (${serial})`) + } + var updates = [] + // Update fields based on given body + if (_.has(body, 'note')) { + updates.push(dbapi.setDeviceNote(serial, body.note)) + } + if (_.has(body, 'status')) { + switch (body.status) { case 'Disconnected': updates.push(dbapi.setDeviceAbsent(serial)) break @@ -514,19 +514,19 @@ function putDeviceInfoBySerial(req, res) { default: apiutil.respond(res, 400, 'Unknown status requested') break + } } - } - if (updates.length === 0) { - return apiutil.respond(res, 400, 'No content to update') - } - return Promise.all(updates) - .then(function() { - apiutil.respond(res, 200) + if (updates.length === 0) { + return apiutil.respond(res, 400, 'No content to update') + } + return Promise.all(updates) + .then(function() { + apiutil.respond(res, 200) + }) }) - }) .catch(function(err) { - apiutil.internalError(res, 'Failed to update device: ', err.stack) - }) + apiutil.internalError(res, 'Failed to update device: ', err.stack) + }) } function deleteDevices(req, res) { const serials = apiutil.getBodyParameter(req.body, 'serials') @@ -543,20 +543,20 @@ function deleteDevices(req, res) { }) }) .then(function() { - results = _.without(results, 'unchanged') - if (!results.length) { - return apiutil.respond(res, 200, `Unchanged (${target})`) - } - if (!_.without(results, 'not found').length) { - return apiutil.respond(res, 404, `Not Found (${target})`) - } - return apiutil.respond(res, 200, `Deleted (${target})`) - }) + results = _.without(results, 'unchanged') + if (!results.length) { + return apiutil.respond(res, 200, `Unchanged (${target})`) + } + if (!_.without(results, 'not found').length) { + return apiutil.respond(res, 404, `Not Found (${target})`) + } + return apiutil.respond(res, 200, `Deleted (${target})`) + }) .catch(function(err) { - if (err !== 'busy') { - throw err - } - }) + if (err !== 'busy') { + throw err + } + }) } (function() { if (typeof serials === 'undefined') { @@ -571,8 +571,8 @@ function deleteDevices(req, res) { } })() .catch(function(err) { - apiutil.internalError(res, `Failed to delete ${target}: `, err.stack) - }) + apiutil.internalError(res, `Failed to delete ${target}: `, err.stack) + }) } function putDeviceBySerial(req, res) { apiutil.redirectApiWrapper('serial', putDeviceInfoBySerial, req, res) @@ -587,25 +587,25 @@ function updateStorageInfo(req, res) { const adbPort = parseInt(req.query.adbPort, 10) dbapi.loadDeviceBySerial(serial) .then((data) => { - if (data) { - if (storageId) { - dbapi.setDeviceStorageId(serial, storageId) - } - if (place) { - dbapi.setDevicePlace(serial, place) + if (data) { + if (storageId) { + dbapi.setDeviceStorageId(serial, storageId) + } + if (place) { + dbapi.setDevicePlace(serial, place) + } + if (adbPort) { + dbapi.setAdbPort(serial, adbPort) + } + apiutil.respond(res, 200, 'Device info updated') } - if (adbPort) { - dbapi.setAdbPort(serial, adbPort) + else { + apiutil.respond(res, 400, 'Device is not exist') } - apiutil.respond(res, 200, 'Device info updated') - } - else { - apiutil.respond(res, 400, 'Device is not exist') - } - }) + }) .catch(function(err) { - apiutil.internalError(res, 'Failed to update device: ', err.stack) - }) + apiutil.internalError(res, 'Failed to update device: ', err.stack) + }) } function getAdbRange(req, res) { apiutil.respond(res, 200, 'Selected adb range', {adbRange: dbapi.getAdbRange()}) @@ -614,15 +614,15 @@ function renewAdbPort(req, res) { const serial = req.params.serial dbapi.initiallySetAdbPort(serial) .then(port => { - if (port) { - log.info('Applied adb port ' + port + ' for ' + serial) - apiutil.respond(res, 200, 'Adb port updated', {port: port}) - } - else { - log.warn('Cant apply port for ' + serial) - apiutil.respond(res, 200, 'Adb port update failed', {port: null}) - } - }) + if (port) { + log.info('Applied adb port ' + port + ' for ' + serial) + apiutil.respond(res, 200, 'Adb port updated', {port: port}) + } + else { + log.warn('Cant apply port for ' + serial) + apiutil.respond(res, 200, 'Adb port update failed', {port: null}) + } + }) } export {getDevices} export {putDeviceBySerial} diff --git a/lib/units/api/controllers/groups.js b/lib/units/api/controllers/groups.js index 3bfe794ec2..c69139f3fd 100644 --- a/lib/units/api/controllers/groups.js +++ b/lib/units/api/controllers/groups.js @@ -3,10 +3,10 @@ **/ import _ from 'lodash' -import dbapi from '../../../db/api.mjs' -import apiutil from '../../../util/apiutil.js' +import * as dbapi from '../../../db/api.js' +import * as apiutil from '../../../util/apiutil.js' import logger from '../../../util/logger.js' -import lockutil from '../../../util/lockutil.js' +import * as lockutil from '../../../util/lockutil.js' import util from 'util' import uuid from 'uuid' import Promise from 'bluebird' @@ -25,8 +25,8 @@ function groupApiWrapper(email, fn, req, res) { } }) .catch(function(err) { - apiutil.internalError(res, 'Failed to wrap "%s": ', fn.name, err.stack) - }) + apiutil.internalError(res, 'Failed to wrap "%s": ', fn.name, err.stack) + }) } function getDevice(req, serial) { return dbapi.loadDeviceBySerial(serial).then(function(device) { @@ -90,18 +90,18 @@ function checkSchedule(res, oldGroup, _class, email, repetitions, privilege, sta return Promise.resolve(apiutil.respond(res, 400, 'Bad Request (Invalid Life time & class combination: life time > class duration)')) } switch (_class) { - case apiutil.BOOKABLE: - case apiutil.STANDARD: - case apiutil.ONCE: - if (repetitions !== 0) { - return Promise.resolve(apiutil.respond(res, 400, 'Bad Request (Invalid class & repetitions combination)')) - } - break - default: - if (repetitions === 0) { - return Promise.resolve(apiutil.respond(res, 400, 'Bad Request (Invalid class & repetitions combination)')) - } - break + case apiutil.BOOKABLE: + case apiutil.STANDARD: + case apiutil.ONCE: + if (repetitions !== 0) { + return Promise.resolve(apiutil.respond(res, 400, 'Bad Request (Invalid class & repetitions combination)')) + } + break + default: + if (repetitions === 0) { + return Promise.resolve(apiutil.respond(res, 400, 'Bad Request (Invalid class & repetitions combination)')) + } + break } return dbapi.loadUser(email).then(function(owner) { if (repetitions > owner.groups.quotas.repetitions) { @@ -148,46 +148,46 @@ function addGroupDevices(req, res) { let group = lockedGroup return dbapi.addGroupDevices(group, serials) .then(autotestsGroup => { - if (isInternal) { - dbapi.loadDevicesBySerials(autotestsGroup.devices) - .then(devices => apiutil.respond(res, 200, `Added (group ${target})`, {group: {id: autotestsGroup.id, devices: devices}})) - } - else { - apiutil.respond(res, 200, `Added (group ${target})`, {group: apiutil.publishGroup(autotestsGroup)}) - } - }) + if (isInternal) { + dbapi.loadDevicesBySerials(autotestsGroup.devices) + .then(devices => apiutil.respond(res, 200, `Added (group ${target})`, {group: {id: autotestsGroup.id, devices: devices}})) + } + else { + apiutil.respond(res, 200, `Added (group ${target})`, {group: apiutil.publishGroup(autotestsGroup)}) + } + }) .catch(function(err) { - log.error(err) - if (err === 'quota is reached') { - apiutil.respond(res, 403, 'Forbidden (groups duration quota is reached)') - let request = { - body: { - ids: group.id - } - , user: req.user - , query: { - redirected: true + log.error(err) + if (err === 'quota is reached') { + apiutil.respond(res, 403, 'Forbidden (groups duration quota is reached)') + let request = { + body: { + ids: group.id + } + , user: req.user + , query: { + redirected: true + } } + deleteGroups(request, res) } - deleteGroups(request, res) - } - else if (Array.isArray(err)) { - apiutil.respond(res, 409, 'Conflicts Information', {conflicts: err}) - let request = { - body: { - ids: group.id - } - , user: req.user - , query: { - redirected: true + else if (Array.isArray(err)) { + apiutil.respond(res, 409, 'Conflicts Information', {conflicts: err}) + let request = { + body: { + ids: group.id + } + , user: req.user + , query: { + redirected: true + } } + deleteGroups(request, res) } - deleteGroups(request, res) - } - else if (err !== 'busy') { - throw err - } - }) + else if (err !== 'busy') { + throw err + } + }) } lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) { if (lockingSuccessed) { @@ -243,14 +243,14 @@ function addGroupDevices(req, res) { return false }) .catch(function(err) { - apiutil.internalError(res, `Failed to add group ${target}: `, err.stack) - }) + apiutil.internalError(res, `Failed to add group ${target}: `, err.stack) + }) .finally(function() { - lockutil.unlockGroup(lock) - if (email) { - groupApiWrapper(email, addGroupDevices, req, res) - } - }) + lockutil.unlockGroup(lock) + if (email) { + groupApiWrapper(email, addGroupDevices, req, res) + } + }) } function addGroupDevice(req, res) { apiutil.redirectApiWrapper('serial', addGroupDevices, req, res) @@ -296,11 +296,11 @@ function removeGroupDevices(req, res) { return false }) .catch(function(err) { - apiutil.internalError(res, `Failed to remove group ${target}: `, err.stack) - }) + apiutil.internalError(res, `Failed to remove group ${target}: `, err.stack) + }) .finally(function() { - lockutil.unlockGroup(lock) - }) + lockutil.unlockGroup(lock) + }) } function removeGroupDevice(req, res) { apiutil.redirectApiWrapper('serial', removeGroupDevices, req, res) @@ -322,8 +322,8 @@ function getGroupDevice(req, res) { } }) .catch(function(err) { - apiutil.internalError(res, 'Failed to get group device: ', err.stack) - }) + apiutil.internalError(res, 'Failed to get group device: ', err.stack) + }) } function getGroupUser(req, res) { const id = req.params.id @@ -340,8 +340,8 @@ function getGroupUser(req, res) { } }) .catch(function(err) { - apiutil.internalError(res, 'Failed to get group user: ', err.stack) - }) + apiutil.internalError(res, 'Failed to get group user: ', err.stack) + }) } function getGroupUsers(req, res) { const id = req.params.id @@ -356,13 +356,13 @@ function getGroupUsers(req, res) { }) }) .then(function(users) { - apiutil.respond(res, 200, 'Users Information', {users: users}) - }) + apiutil.respond(res, 200, 'Users Information', {users: users}) + }) } }) .catch(function(err) { - apiutil.internalError(res, 'Failed to get group users: ', err.stack) - }) + apiutil.internalError(res, 'Failed to get group users: ', err.stack) + }) } function removeGroupUsers(req, res) { const id = req.params.id @@ -384,12 +384,12 @@ function removeGroupUsers(req, res) { lock.user = stats.changes[0].new_val return dbapi.isRemoveGroupUserAllowed(email, group) .then(function(isAllowed) { - return isAllowed ? dbapi.removeGroupUser(id, email) : 'forbidden' - }) + return isAllowed ? dbapi.removeGroupUser(id, email) : 'forbidden' + }) }) .finally(function() { - lockutil.unlockUser(lock) - }) + lockutil.unlockUser(lock) + }) } lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) { if (lockingSuccessed) { @@ -406,37 +406,37 @@ function removeGroupUsers(req, res) { }) }) .then(function() { - if (!results.length) { - return apiutil.respond(res, 200, `Unchanged (group ${target})`, {group: {}}) - } - results = _.without(results, 'not found') - if (!results.length) { - return apiutil.respond(res, 404, `Not Found (group ${target})`) - } - if (!_.without(results, 'forbidden').length) { - return apiutil.respond(res, 403, `Forbidden (group ${target})`) - } - return dbapi.getGroup(id).then(function(group) { - apiutil.respond(res, 200, `Removed (group ${target})`, { - group: apiutil.publishGroup(group) + if (!results.length) { + return apiutil.respond(res, 200, `Unchanged (group ${target})`, {group: {}}) + } + results = _.without(results, 'not found') + if (!results.length) { + return apiutil.respond(res, 404, `Not Found (group ${target})`) + } + if (!_.without(results, 'forbidden').length) { + return apiutil.respond(res, 403, `Forbidden (group ${target})`) + } + return dbapi.getGroup(id).then(function(group) { + apiutil.respond(res, 200, `Removed (group ${target})`, { + group: apiutil.publishGroup(group) + }) }) }) - }) }) .catch(function(err) { - if (err !== 'busy') { - throw err - } - }) + if (err !== 'busy') { + throw err + } + }) } return false }) .catch(function(err) { - apiutil.internalError(res, `Failed to remove group ${target}: `, err.stack) - }) + apiutil.internalError(res, `Failed to remove group ${target}: `, err.stack) + }) .finally(function() { - lockutil.unlockGroup(lock) - }) + lockutil.unlockGroup(lock) + }) } function removeGroupUser(req, res) { apiutil.redirectApiWrapper('email', removeGroupUsers, req, res) @@ -456,8 +456,8 @@ function addGroupUsers(req, res) { return dbapi.addGroupUser(id, email) }) .finally(function() { - lockutil.unlockUser(lock) - }) + lockutil.unlockUser(lock) + }) } function _addGroupUsers(emails) { let results = [] @@ -467,22 +467,22 @@ function addGroupUsers(req, res) { }) }) .then(function() { - results = _.without(results, 'unchanged') - if (!results.length) { - return apiutil.respond(res, 200, `Unchanged (group ${target})`, {group: {}}) - } - if (!_.without(results, 'not found').length) { - return apiutil.respond(res, 404, `Not Found (group ${target})`) - } - return dbapi.getGroup(id).then(function(group) { - apiutil.respond(res, 200, `Added (group ${target})`, {group: apiutil.publishGroup(group)}) + results = _.without(results, 'unchanged') + if (!results.length) { + return apiutil.respond(res, 200, `Unchanged (group ${target})`, {group: {}}) + } + if (!_.without(results, 'not found').length) { + return apiutil.respond(res, 404, `Not Found (group ${target})`) + } + return dbapi.getGroup(id).then(function(group) { + apiutil.respond(res, 200, `Added (group ${target})`, {group: apiutil.publishGroup(group)}) + }) }) - }) .catch(function(err) { - if (err !== 'busy') { - throw err - } - }) + if (err !== 'busy') { + throw err + } + }) } lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) { if (!lockingSuccessed) { @@ -507,11 +507,11 @@ function addGroupUsers(req, res) { })() }) .catch(function(err) { - apiutil.internalError(res, `Failed to add group ${target}: `, err.stack) - }) + apiutil.internalError(res, `Failed to add group ${target}: `, err.stack) + }) .finally(function() { - lockutil.unlockGroup(lock) - }) + lockutil.unlockGroup(lock) + }) } function addGroupUser(req, res) { apiutil.redirectApiWrapper('email', addGroupUsers, req, res) @@ -531,22 +531,22 @@ function getGroup(req, res) { apiutil.respond(res, 200, 'Group Information', {group: publishedGroup}) }) .catch(function(err) { - apiutil.internalError(res, 'Failed to get group: ', err.stack) - }) + apiutil.internalError(res, 'Failed to get group: ', err.stack) + }) } function getGroups(req, res) { const fields = req.query.fields const owner = req.query.owner let getGenericGroups switch (owner) { - case true: - getGenericGroups = dbapi.getOwnerGroups - break - case false: - getGenericGroups = dbapi.getOnlyUserGroups - break - default: - getGenericGroups = dbapi.getUserGroups + case true: + getGenericGroups = dbapi.getOwnerGroups + break + case false: + getGenericGroups = dbapi.getOnlyUserGroups + break + default: + getGenericGroups = dbapi.getUserGroups } getGenericGroups(req.user.email).then(function(groups) { return apiutil.respond(res, 200, 'Groups Information', { @@ -559,48 +559,49 @@ function getGroups(req, res) { }) }) .catch(function(err) { - apiutil.internalError(res, 'Failed to get groups: ', err.stack) - }) + apiutil.internalError(res, 'Failed to get groups: ', err.stack) + }) } function createGroupFunc( - res, - _class, - email, - repetitions, - name, - username, - privilege, - isActive, - dates, - start, - stop, - duration, - state, - runUrl + res, + _class, + email, + repetitions, + name, + username, + privilege, + isActive, + dates, + start, + stop, + duration, + state, + runUrl ) { - checkSchedule(res, null, _class, email, repetitions, privilege, - start, stop) - .then(function(checkingSuccessed) { - if (!checkingSuccessed) { - return - } - }) + checkSchedule(res, null, _class, email, repetitions, privilege, + start, stop) + .then(function(checkingSuccessed) { + if (!checkingSuccessed) { + log.info('Schedule check fail') + return + } + }) - return dbapi.createUserGroup({ - name: name - , owner: { - email: email - , name: username - } - , privilege: privilege - , class: _class - , repetitions: repetitions - , isActive: isActive - , dates: dates - , duration: duration - , state: state - }) + return dbapi.createUserGroup({ + name: name + , owner: { + email: email + , name: username + } + , privilege: privilege + , class: _class + , repetitions: repetitions + , isActive: isActive + , dates: dates + , duration: duration + , state: state + }) } function createGroup(req, res) { const _class = typeof req.body.class === 'undefined' ? apiutil.ONCE : req.body.class @@ -649,7 +650,7 @@ function deleteGroups(req, res) { isInternal: true , serial: serial , user: req.user - , path: {email: req.user.email} + , params: {email: req.user.email} , options: req.options } usersapi.deleteUserDeviceBySerial(unsetReq, res) @@ -671,21 +672,21 @@ function deleteGroups(req, res) { return Promise.each(group.devices, function(serial) { return dbapi.isDeviceBooked(serial) .then(function(isBooked) { - return isBooked ? Promise.reject('booked') : true - }) + return isBooked ? Promise.reject('booked') : true + }) }) .then(function() { - return dbapi.deleteUserGroup(id).then(deleteResult => { - unsetDevices(group.devices) - return deleteResult + return dbapi.deleteUserGroup(id).then(deleteResult => { + unsetDevices(group.devices) + return deleteResult + }) }) - }) .catch(function(err) { - if (err !== 'booked') { - throw err - } - return 'forbidden' - }) + if (err !== 'booked') { + throw err + } + return 'forbidden' + }) } return dbapi.deleteUserGroup(id).then(deleteResult => { unsetDevices(group.devices) @@ -693,8 +694,8 @@ function deleteGroups(req, res) { }) }) .finally(function() { - lockutil.unlockGroup(lock) - }) + lockutil.unlockGroup(lock) + }) } function removeGroups(ids) { let results = [] @@ -705,24 +706,24 @@ function deleteGroups(req, res) { }) }) .then(function() { - if (!results.length) { - return apiutil.respond(res, 304, `Unchanged (${target})`) - } - results = _.without(results, 'not found') - if (!results.length) { - return apiutil.respond(res, 404, `Not Found (${target})`) - } - results = _.without(results, 'forbidden') - if (!results.length) { - return apiutil.respond(res, 403, `Forbidden (${target})`) - } - return apiutil.respond(res, 200, `Deleted (${target})`) - }) + if (!results.length) { + return apiutil.respond(res, 304, `Unchanged (${target})`) + } + results = _.without(results, 'not found') + if (!results.length) { // if everything is "not found" + return apiutil.respond(res, 404, `Not Found (${target})`) + } + results = _.without(results, 'forbidden') + if (!results.length) { // if everything is forbidden + return apiutil.respond(res, 403, `Forbidden (${target})`) + } + return apiutil.respond(res, 200, `Deleted (${target})`) + }) .catch(function(err) { - if (err !== 'busy') { - throw err - } - }) + if (err !== 'busy') { + throw err + } + }) } (function() { if (typeof ids === 'undefined') { @@ -741,8 +742,8 @@ function deleteGroups(req, res) { } })() .catch(function(err) { - apiutil.internalError(res, `Failed to delete ${target}: `, err.stack) - }) + apiutil.internalError(res, `Failed to delete ${target}: `, err.stack) + }) } function deleteGroup(req, res) { apiutil.redirectApiWrapper('id', deleteGroups, req, res) @@ -754,19 +755,19 @@ function updateGroup(req, res) { function updateUserGroup(group, data) { return dbapi.updateUserGroup(group, data) .then(function(group) { - if (group) { - if (isInternal) { - dbapi.loadDevicesBySerials(group.devices) - .then(devices => apiutil.respond(res, 200, 'Updated (group)', {group: {id: group.id, devices: devices}})) + if (group) { + if (isInternal) { + dbapi.loadDevicesBySerials(group.devices) + .then(devices => apiutil.respond(res, 200, 'Updated (group)', {group: {id: group.id, devices: devices}})) + } + else { + apiutil.respond(res, 200, 'Updated (group)', {group: apiutil.publishGroup(group)}) + } } else { - apiutil.respond(res, 200, 'Updated (group)', {group: apiutil.publishGroup(group)}) + apiutil.respond(res, 403, 'Forbidden (groups duration quota is reached)') } - } - else { - apiutil.respond(res, 403, 'Forbidden (groups duration quota is reached)') - } - }) + }) } lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) { if (!lockingSuccessed) { @@ -801,14 +802,14 @@ function updateGroup(req, res) { return dbapi.updateGroup(group.id, { name: name }) - .then(function(updatedGroup) { - if (updatedGroup) { - apiutil.respond(res, 200, 'Updated (group)', {group: apiutil.publishGroup(updatedGroup)}) - } - else { - throw new Error(`Group not found: ${group.id}`) - } - }) + .then(function(updatedGroup) { + if (updatedGroup) { + apiutil.respond(res, 200, 'Updated (group)', {group: apiutil.publishGroup(updatedGroup)}) + } + else { + throw new Error(`Group not found: ${group.id}`) + } + }) } if (apiutil.isOriginGroup(_class)) { state = apiutil.READY @@ -840,7 +841,7 @@ function updateGroup(req, res) { } } const duration = apiutil.isOriginGroup(_class) ? - 0 : group.devices.length * (stop - start) * (repetitions + 1) + 0 : group.devices.length * (stop - start) * (repetitions + 1) const dates = apiutil.computeGroupDates({start: start, stop: stop}, _class, repetitions) if (start < group.dates[0].start || stop > group.dates[0].stop || @@ -848,19 +849,19 @@ function updateGroup(req, res) { _class !== group.class) { return checkConflicts(id, group.devices, dates) .then(function(conflicts) { - if (!conflicts.length) { - return updateUserGroup(group, { - name: name - , state: state - , class: _class - , isActive: isActive - , repetitions: repetitions - , dates: dates - , duration: duration - }) - } - return apiutil.respond(res, 409, 'Conflicts Information', {conflicts: conflicts}) - }) + if (!conflicts.length) { + return updateUserGroup(group, { + name: name + , state: state + , class: _class + , isActive: isActive + , repetitions: repetitions + , dates: dates + , duration: duration + }) + } + return apiutil.respond(res, 409, 'Conflicts Information', {conflicts: conflicts}) + }) } return updateUserGroup(group, { name: name @@ -874,11 +875,11 @@ function updateGroup(req, res) { }) }) .catch(function(err) { - apiutil.internalError(res, 'Failed to update group: ', err.stack) - }) + apiutil.internalError(res, 'Failed to update group: ', err.stack) + }) .finally(function() { - lockutil.unlockGroup(lock) - }) + lockutil.unlockGroup(lock) + }) } function getGroupDevices(req, res) { const id = req.params.id @@ -902,24 +903,24 @@ function getGroupDevices(req, res) { return device.serial }) .then(function(serials) { - return checkConflicts(group.id, serials, group.dates) - .then(function(conflicts) { - let bookableSerials = serials - conflicts.forEach(function(conflict) { - bookableSerials = _.difference(bookableSerials, conflict.devices) - }) - return bookableSerials + return checkConflicts(group.id, serials, group.dates) + .then(function(conflicts) { + let bookableSerials = serials + conflicts.forEach(function(conflict) { + bookableSerials = _.difference(bookableSerials, conflict.devices) + }) + return bookableSerials + }) }) - }) .then(function(bookableSerials) { - const deviceList = [] - devices.forEach(function(device) { - if (bookableSerials.indexOf(device.serial) > -1) { - deviceList.push(apiutil.filterDevice(req, device)) - } + const deviceList = [] + devices.forEach(function(device) { + if (bookableSerials.indexOf(device.serial) > -1) { + deviceList.push(apiutil.filterDevice(req, device)) + } + }) + apiutil.respond(res, 200, 'Devices Information', {devices: deviceList}) }) - apiutil.respond(res, 200, 'Devices Information', {devices: deviceList}) - }) }) } else { @@ -927,13 +928,13 @@ function getGroupDevices(req, res) { return getDevice(req, serial) }) .then(function(devices) { - apiutil.respond(res, 200, 'Devices Information', {devices: devices}) - }) + apiutil.respond(res, 200, 'Devices Information', {devices: devices}) + }) } }) .catch(function(err) { - apiutil.internalError(res, 'Failed to get group devices: ', err.stack) - }) + apiutil.internalError(res, 'Failed to get group devices: ', err.stack) + }) } export {createGroup} export {createGroupFunc} diff --git a/lib/units/api/controllers/stats.js b/lib/units/api/controllers/stats.js index 20c95021d0..c5ce718dac 100644 --- a/lib/units/api/controllers/stats.js +++ b/lib/units/api/controllers/stats.js @@ -1,16 +1,16 @@ -import apiutil from '../../../util/apiutil.js' -import dbapi from '../../../db/api.mjs' +import * as apiutil from '../../../util/apiutil.js' +import * as dbapi from '../../../db/api.js' function writeStats(req, res) { const user = req.user.name const serial = req.query.serial const action = req.query.action dbapi.writeStats(user, serial, action) .then((r) => { - apiutil.respond(res, 200, r) - }) + apiutil.respond(res, 200, r) + }) .catch(() => { - apiutil.internalError(res, 'Failed') - }) + apiutil.internalError(res, 'Failed') + }) } export {writeStats} export default { diff --git a/lib/units/api/controllers/user.js b/lib/units/api/controllers/user.js index b2df4b1df7..71d8fa79c3 100644 --- a/lib/units/api/controllers/user.js +++ b/lib/units/api/controllers/user.js @@ -1,19 +1,20 @@ import util from 'util' import _ from 'lodash' -import Promise from 'bluebird' import uuid from 'uuid' import adbkit from '@devicefarmer/adbkit' -import dbapi from '../../../db/api.mjs' +import * as dbapi from '../../../db/api.js' import logger from '../../../util/logger.js' import datautil from '../../../util/datautil.js' import deviceutil from '../../../util/deviceutil.js' import wire from '../../../wire/index.js' import wireutil from '../../../wire/util.js' import wirerouter from '../../../wire/router.js' -import apiutil from '../../../util/apiutil.js' +import * as apiutil from '../../../util/apiutil.js' import * as jwtutil from '../../../util/jwtutil.js' -import lockutil from '../../../util/lockutil.js' +import * as lockutil from '../../../util/lockutil.js' +import * as Sentry from '@sentry/node' let log = logger.createLogger('api:controllers:user') + function getUser(req, res) { // delete req.user.groups.lock res.json({ @@ -21,72 +22,75 @@ function getUser(req, res) { , user: req.user }) } + function getUserDevices(req, res) { var fields = req.query.fields log.info('Loading user devices') dbapi.loadUserDevices(req.user.email) .then(list => { - log.info('Devices list from db - ' + list) - let deviceList = [] - list.forEach(function(device) { - datautil.normalize(device, req.user) - let responseDevice = device - if (fields) { - responseDevice = _.pick(device, fields.split(',')) - } - deviceList.push(responseDevice) - }) - log.info('Devices list after normalization - ' + deviceList) - res.json({ - success: true - , description: 'Information about controlled devices' - , devices: deviceList + log.info('Devices list from db - ' + list) + let deviceList = [] + list.forEach(function(device) { + datautil.normalize(device, req.user) + let responseDevice = device + if (fields) { + responseDevice = _.pick(device, fields.split(',')) + } + deviceList.push(responseDevice) + }) + log.info('Devices list after normalization - ' + deviceList) + res.json({ + success: true + , description: 'Information about controlled devices' + , devices: deviceList + }) }) - }) .catch(err => { - log.error('Failed to load device list: ', err.stack) - res.status(500).json({ - success: false - , description: 'Internal Server Error' + log.error('Failed to load device list: ', err.stack) + res.status(500).json({ + success: false + , description: 'Internal Server Error' + }) }) - }) } + function getUserDeviceBySerial(req, res) { var serial = req.params.serial var fields = req.query.fields dbapi.loadDevice(req.user.groups.subscribed, serial) .then(function(device) { - if (!device) { - return res.status(404).json({ - success: false - , description: 'Device not found' - }) - } - datautil.normalize(device, req.user) - if (!deviceutil.isOwnedByUser(device, req.user)) { - return res.status(403).json({ - success: false - , description: 'Device is not owned by you' + if (!device) { + return res.status(404).json({ + success: false + , description: 'Device not found' + }) + } + datautil.normalize(device, req.user) + if (!deviceutil.isOwnedByUser(device, req.user)) { + return res.status(403).json({ + success: false + , description: 'Device is not owned by you' + }) + } + var responseDevice = device + if (fields) { + responseDevice = _.pick(device, fields.split(',')) + } + res.json({ + success: true + , description: 'Controlled device information' + , device: responseDevice }) - } - var responseDevice = device - if (fields) { - responseDevice = _.pick(device, fields.split(',')) - } - res.json({ - success: true - , description: 'Controlled device information' - , device: responseDevice }) - }) .catch(function(err) { - log.error('Failed to load device "%s": ', req.params.serial, err.stack) - res.status(500).json({ - success: false - , description: 'Internal Server Error' + log.error('Failed to load device "%s": ', req.params.serial, err.stack) + res.status(500).json({ + success: false + , description: 'Internal Server Error' + }) }) - }) } + function addUserDevice(req, res) { let serial = Object.prototype.hasOwnProperty.call(req, 'body') ? req.body.serial : req.params.serial let timeout = Object.prototype.hasOwnProperty.call(req, 'body') ? req.body.timeout || @@ -94,57 +98,58 @@ function addUserDevice(req, res) { const lock = {} lockutil.lockGenericDevice(req, res, lock, dbapi.lockDeviceByCurrent) .then(function(lockingSuccessed) { - if (lockingSuccessed) { - const device = lock.device - datautil.normalize(device, req.user) - if (!deviceutil.isAddable(device, req.user)) { - return res.status(403).json({ - success: false - , description: 'Device is being used or not available' - }) - } - // Timer will be called if no JoinGroupMessage is received till 5 seconds - let responseTimer = setTimeout(function() { - req.options.channelRouter.removeListener(wireutil.global, messageListener) - return res.status(504).json({ - success: false - , description: 'Device is not responding' - }) - }, apiutil.GRPC_WAIT_TIMEOUT) - let messageListener = wirerouter() - .on(wire.JoinGroupMessage, function(channel, message) { - log.info(device.serial + ' added to user group ' + req.user) - if (message.serial === serial && message.owner.email === req.user.email) { - clearTimeout(responseTimer) - req.options.channelRouter.removeListener(wireutil.global, messageListener) - return res.json({ - success: true - , description: 'Device successfully added' + if (lockingSuccessed) { + const device = lock.device + datautil.normalize(device, req.user) + if (!deviceutil.isAddable(device, req.user)) { + return res.status(403).json({ + success: false + , description: 'Device is being used or not available' }) } - }) - .handler() - req.options.channelRouter.on(wireutil.global, messageListener) - let usage = 'automation' - req.options.push.send([ - device.channel - , wireutil.envelope(new wire.GroupMessage(new wire.OwnerMessage(req.user.email, req.user.name, req.user.group), timeout, wireutil.toDeviceRequirements({ - serial: { - value: serial - , match: 'exact' - } - }), usage)) - ]) - } - return false - }) + // Timer will be called if no JoinGroupMessage is received till 5 seconds + let responseTimer = setTimeout(function() { + req.options.channelRouter.removeListener(wireutil.global, messageListener) + return res.status(504).json({ + success: false + , description: 'Device is not responding' + }) + }, apiutil.GRPC_WAIT_TIMEOUT) + let messageListener = wirerouter() + .on(wire.JoinGroupMessage, function(channel, message) { + log.info(device.serial + ' added to user group ' + req.user) + if (message.serial === serial && message.owner.email === req.user.email) { + clearTimeout(responseTimer) + req.options.channelRouter.removeListener(wireutil.global, messageListener) + return res.json({ + success: true + , description: 'Device successfully added' + }) + } + }) + .handler() + req.options.channelRouter.on(wireutil.global, messageListener) + let usage = 'automation' + req.options.push.send([ + device.channel + , wireutil.envelope(new wire.GroupMessage(new wire.OwnerMessage(req.user.email, req.user.name, req.user.group), timeout, wireutil.toDeviceRequirements({ + serial: { + value: serial + , match: 'exact' + } + }), usage)) + ]) + } + return false + }) .catch(function(err) { - apiutil.internalError(res, `Failed to take control of ${serial} device: `, err.stack) - }) + apiutil.internalError(res, `Failed to take control of ${serial} device: `, err.stack) + }) .finally(function() { - lockutil.unlockDevice(lock) - }) + lockutil.unlockDevice(lock) + }) } + function deleteUserDeviceBySerial(req, res) { const isInternal = req.isInternal let serial @@ -156,147 +161,156 @@ function deleteUserDeviceBySerial(req, res) { } dbapi.loadDevice(req.user.groups.subscribed, serial) .then(function(device) { - if (!device) { - if (isInternal) { - return false + if (!device) { + if (isInternal) { + return false + } + else { + return res.status(404).json({ + success: false + , description: 'Device not found' + }) + } } - else { - return res.status(404).json({ - success: false - , description: 'Device not found' + datautil.normalize(device, req.user) + if (!deviceutil.isOwnedByUser(device, req.user)) { + Sentry.addBreadcrumb({ + data: {device, user: req.user} + , message: 'This device is not owned by this user.' + , level: 'warning' + , type: 'default' }) + if (isInternal) { + return false + } + else { + Sentry.captureMessage('403 someone tried to release somebody elses device') + return res.status(403).json({ + success: false + , description: 'Releasing this device is not possible as it does not belong to you' + }) + } } - } - datautil.normalize(device, req.user) - if (!deviceutil.isOwnedByUser(device, req.user)) { + // Timer will be called if no JoinGroupMessage is received till 5 seconds + var responseTimer = setTimeout(function() { + req.options.channelRouter.removeListener(wireutil.global, messageListener) + if (isInternal) { + return false + } + else { + return res.status(504).json({ + success: false + , description: 'Device is not responding' + }) + } + }, apiutil.GRPC_WAIT_TIMEOUT) + var messageListener = wirerouter() + .on(wire.LeaveGroupMessage, function(channel, message) { + if (message.serial === serial && + (message.owner.email === req.user.email || req.user.privilege === 'admin')) { + clearTimeout(responseTimer) + req.options.channelRouter.removeListener(wireutil.global, messageListener) + if (isInternal) { + return true + } + else { + return res.json({ + success: true + , description: 'Device successfully removed' + }) + } + } + }) + .handler() + req.options.channelRouter.on(wireutil.global, messageListener) + req.options.push.send([ + device.channel + , wireutil.envelope(new wire.UngroupMessage(wireutil.toDeviceRequirements({ + serial: { + value: serial + , match: 'exact' + } + }))) + ]) + }) + .catch(function(err) { + let errSerial if (isInternal) { - return false + errSerial = req.serial } else { - return res.status(403).json({ - success: false - , description: 'Releasing this device is not possible as it does not belong to you' - }) + errSerial = req.params.serial } - } - // Timer will be called if no JoinGroupMessage is received till 5 seconds - var responseTimer = setTimeout(function() { - req.options.channelRouter.removeListener(wireutil.global, messageListener) + log.error('Failed to load device "%s": ', errSerial, err.stack) if (isInternal) { return false } else { - return res.status(504).json({ + res.status(500).json({ success: false - , description: 'Device is not responding' + , description: 'Internal Server Error' }) } - }, apiutil.GRPC_WAIT_TIMEOUT) - var messageListener = wirerouter() - .on(wire.LeaveGroupMessage, function(channel, message) { - if (message.serial === serial && - (message.owner.email === req.user.email || req.user.privilege === 'admin')) { - clearTimeout(responseTimer) - req.options.channelRouter.removeListener(wireutil.global, messageListener) - if (isInternal) { - return true - } - else { - return res.json({ - success: true - , description: 'Device successfully removed' - }) - } - } }) - .handler() - req.options.channelRouter.on(wireutil.global, messageListener) - req.options.push.send([ - device.channel - , wireutil.envelope(new wire.UngroupMessage(wireutil.toDeviceRequirements({ - serial: { - value: serial - , match: 'exact' - } - }))) - ]) - }) - .catch(function(err) { - let errSerial - if (isInternal) { - errSerial = req.serial - } - else { - errSerial = req.params.serial - } - log.error('Failed to load device "%s": ', errSerial, err.stack) - if (isInternal) { - return false - } - else { - res.status(500).json({ - success: false - , description: 'Internal Server Error' - }) - } - }) } + function remoteConnectUserDeviceBySerial(req, res) { let serial = req.params.serial dbapi.loadDevice(req.user.groups.subscribed, serial) .then(function(device) { - if (!device) { - return res.status(404).json({ - success: false - , description: 'Device not found' - }) - } - datautil.normalize(device, req.user) - if (!deviceutil.isOwnedByUser(device, req.user)) { - return res.status(403).json({ - success: false - , description: 'Device is not owned by you or is not available' - }) - } - let responseChannel = 'txn_' + uuid.v4() - req.options.sub.subscribe(responseChannel) - // Timer will be called if no JoinGroupMessage is received till 5 seconds - let timer = setTimeout(function() { - req.options.channelRouter.removeListener(responseChannel, messageListener) - req.options.sub.unsubscribe(responseChannel) - return res.status(504).json({ - success: false - , description: 'Device is not responding' - }) - }, apiutil.GRPC_WAIT_TIMEOUT) - let messageListener = wirerouter() - .on(wire.ConnectStartedMessage, function(channel, message) { - if (message.serial === serial) { - clearTimeout(timer) - req.options.sub.unsubscribe(responseChannel) - req.options.channelRouter.removeListener(responseChannel, messageListener) - return res.json({ - success: true - , description: 'Remote connection is enabled' - , remoteConnectUrl: message.url + if (!device) { + return res.status(404).json({ + success: false + , description: 'Device not found' + }) + } + datautil.normalize(device, req.user) + if (!deviceutil.isOwnedByUser(device, req.user)) { + return res.status(403).json({ + success: false + , description: 'Device is not owned by you or is not available' }) } + let responseChannel = 'txn_' + uuid.v4() + req.options.sub.subscribe(responseChannel) + // Timer will be called if no JoinGroupMessage is received till 5 seconds + let timer = setTimeout(function() { + req.options.channelRouter.removeListener(responseChannel, messageListener) + req.options.sub.unsubscribe(responseChannel) + return res.status(504).json({ + success: false + , description: 'Device is not responding' + }) + }, apiutil.GRPC_WAIT_TIMEOUT) + let messageListener = wirerouter() + .on(wire.ConnectStartedMessage, function(channel, message) { + if (message.serial === serial) { + clearTimeout(timer) + req.options.sub.unsubscribe(responseChannel) + req.options.channelRouter.removeListener(responseChannel, messageListener) + return res.json({ + success: true + , description: 'Remote connection is enabled' + , remoteConnectUrl: message.url + }) + } + }) + .handler() + req.options.channelRouter.on(responseChannel, messageListener) + req.options.push.send([ + device.channel + , wireutil.transaction(responseChannel, new wire.ConnectStartMessage()) + ]) }) - .handler() - req.options.channelRouter.on(responseChannel, messageListener) - req.options.push.send([ - device.channel - , wireutil.transaction(responseChannel, new wire.ConnectStartMessage()) - ]) - }) .catch(function(err) { - log.error('Failed to load device "%s": ', req.params.serial, err.stack) - res.status(500).json({ - success: false - , description: 'Internal Server Error' + log.error('Failed to load device "%s": ', req.params.serial, err.stack) + res.status(500).json({ + success: false + , description: 'Internal Server Error' + }) }) - }) } + function remoteDisconnectUserDeviceBySerial(req, res) { const isInternal = req.isInternal let serial @@ -308,118 +322,120 @@ function remoteDisconnectUserDeviceBySerial(req, res) { } dbapi.loadDevice(req.user.groups.subscribed, serial) .then(function(device) { - if (!device) { - if (isInternal) { - return false - } - else { - return res.status(404).json({ - success: false - , description: 'Device not found' - }) - } - } - datautil.normalize(device, req.user) - if (!deviceutil.isOwnedByUser(device, req.user)) { - if (isInternal) { - return false - } - else { - return res.status(403).json({ - success: false - , description: 'Device is not owned by you or is not available' - }) - } - } - var responseChannel = 'txn_' + uuid.v4() - req.options.sub.subscribe(responseChannel) - // Timer will be called if no JoinGroupMessage is received till 5 seconds - var timer = setTimeout(function() { - req.options.channelRouter.removeListener(responseChannel, messageListener) - req.options.sub.unsubscribe(responseChannel) - if (isInternal) { - return false + if (!device) { + if (isInternal) { + return false + } + else { + return res.status(404).json({ + success: false + , description: 'Device not found' + }) + } } - else { - return res.status(504).json({ - success: false - , description: 'Device is not responding' - }) + datautil.normalize(device, req.user) + if (!deviceutil.isOwnedByUser(device, req.user)) { + if (isInternal) { + return false + } + else { + return res.status(403).json({ + success: false + , description: 'Device is not owned by you or is not available' + }) + } } - }, apiutil.GRPC_WAIT_TIMEOUT) - var messageListener = wirerouter() - .on(wire.ConnectStoppedMessage, function(channel, message) { - if (message.serial === serial) { - clearTimeout(timer) - req.options.sub.unsubscribe(responseChannel) + var responseChannel = 'txn_' + uuid.v4() + req.options.sub.subscribe(responseChannel) + // Timer will be called if no JoinGroupMessage is received till 5 seconds + var timer = setTimeout(function() { req.options.channelRouter.removeListener(responseChannel, messageListener) + req.options.sub.unsubscribe(responseChannel) if (isInternal) { - return true + return false } else { - return res.json({ - success: true - , description: 'Device remote disconnected successfully' + return res.status(504).json({ + success: false + , description: 'Device is not responding' }) } - } + }, apiutil.GRPC_WAIT_TIMEOUT) + var messageListener = wirerouter() + .on(wire.ConnectStoppedMessage, function(channel, message) { + if (message.serial === serial) { + clearTimeout(timer) + req.options.sub.unsubscribe(responseChannel) + req.options.channelRouter.removeListener(responseChannel, messageListener) + if (isInternal) { + return true + } + else { + return res.json({ + success: true + , description: 'Device remote disconnected successfully' + }) + } + } + }) + .handler() + req.options.channelRouter.on(responseChannel, messageListener) + req.options.push.send([ + device.channel + , wireutil.transaction(responseChannel, new wire.ConnectStopMessage()) + ]) }) - .handler() - req.options.channelRouter.on(responseChannel, messageListener) - req.options.push.send([ - device.channel - , wireutil.transaction(responseChannel, new wire.ConnectStopMessage()) - ]) - }) .catch(function(err) { - let errSerial - if (isInternal) { - errSerial = req.serial - } - else { - errSerial = req.params.serial - } - log.error('Failed to load device "%s": ', errSerial, err.stack) - res.status(500).json({ - success: false - , description: 'Internal Server Error' + let errSerial + if (isInternal) { + errSerial = req.serial + } + else { + errSerial = req.params.serial + } + log.error('Failed to load device "%s": ', errSerial, err.stack) + res.status(500).json({ + success: false + , description: 'Internal Server Error' + }) }) - }) } + function getUserAccessTokens(req, res) { return dbapi.loadAccessTokens(req.user.email) .then(function(list) { - var titles = [] - list.forEach(function(token) { - titles.push(token.title) - }) - res.json({ - success: true - , titles: titles + var titles = [] + list.forEach(function(token) { + titles.push(token.title) + }) + res.json({ + success: true + , titles: titles + }) }) - }) .catch(function(err) { - log.error('Failed to load tokens: ', err.stack) - res.status(500).json({ - success: false + log.error('Failed to load tokens: ', err.stack) + res.status(500).json({ + success: false + }) }) - }) } + function addAdbPublicKey(req, res) { var data = req.body adbkit.Adb.util.parsePublicKey(data.publickey).then(function(key) { return dbapi.lookupUsersByAdbKey(key.fingerprint) .then(function(adbKeys) { - return adbKeys - }).then(function(users) { - return { - key: { - title: data.title || key.comment - , fingerprint: key.fingerprint + return adbKeys + }).then(function(users) { + return { + key: { + title: data.title || key.comment + , fingerprint: key.fingerprint + } + , users: users } - , users: users - } - }) + }) }).then(function(data) { if (data.users.length) { return res.json({ @@ -430,11 +446,11 @@ function addAdbPublicKey(req, res) { else { return dbapi.insertUserAdbKey(req.user.email, data.key) .then(function() { - return res.json({ - success: true - , fingerprint: data.key.fingerprint + return res.json({ + success: true + , fingerprint: data.key.fingerprint + }) }) - }) } }).then(function() { req.options.push.send([ @@ -455,23 +471,25 @@ function addAdbPublicKey(req, res) { }) }) } + function removeAdbPublicKey(req, res) { const fingerprint = req.body.fingerprint dbapi.deleteUserAdbKey(req.user.email, fingerprint) .then(() => { // TODO: check that key was really deleted - return res.status(200).json({ - success: true - , message: 'Key with fingerprint ' + fingerprint + ' was deleted' + return res.status(200).json({ + success: true + , message: 'Key with fingerprint ' + fingerprint + ' was deleted' + }) }) - }) .catch(() => { - return res.status(500).json({ - success: false - , message: 'Unable to delete key from database' + return res.status(500).json({ + success: false + , message: 'Unable to delete key from database' + }) }) - }) } + function getAccessToken(req, res) { const id = req.params.id dbapi.loadAccessToken(id).then(function(token) { @@ -485,9 +503,10 @@ function getAccessToken(req, res) { } }) .catch(function(err) { - apiutil.internalError(res, 'Failed to delete access token "%s": ', id, err.stack) - }) + apiutil.internalError(res, 'Failed to delete access token "%s": ', id, err.stack) + }) } + function getAccessTokens(req, res) { dbapi.loadAccessTokens(req.user.email).then(function(cursor) { Promise.promisify(cursor.toArray, cursor)().then(function(tokens) { @@ -499,9 +518,10 @@ function getAccessTokens(req, res) { }) }) .catch(function(err) { - apiutil.internalError(res, 'Failed to get access tokens: ', err.stack) - }) + apiutil.internalError(res, 'Failed to get access tokens: ', err.stack) + }) } + function createAccessToken(req, res) { const title = req.query.title const jwt = jwtutil.encode({ @@ -518,16 +538,17 @@ function createAccessToken(req, res) { , jwt: jwt }) .then(function(stats) { - req.options.pushdev.send([ - req.user.group - , wireutil.envelope(new wire.UpdateAccessTokenMessage()) - ]) - apiutil.respond(res, 201, 'Created (access token)', {token: apiutil.publishAccessToken(stats.changes[0].new_val)}) - }) + req.options.pushdev.send([ + req.user.group + , wireutil.envelope(new wire.UpdateAccessTokenMessage()) + ]) + apiutil.respond(res, 201, 'Created (access token)', {token: apiutil.publishAccessToken(stats.changes[0].new_val)}) + }) .catch(function(err) { - apiutil.internalError(res, 'Failed to create access token "%s": ', title, err.stack) - }) + apiutil.internalError(res, 'Failed to create access token "%s": ', title, err.stack) + }) } + function deleteAccessTokens(req, res) { dbapi.removeUserAccessTokens(req.user.email).then(function(stats) { if (!stats.deleted) { @@ -542,9 +563,10 @@ function deleteAccessTokens(req, res) { } }) .catch(function(err) { - apiutil.internalError(res, 'Failed to delete access tokens: ', err.stack) - }) + apiutil.internalError(res, 'Failed to delete access tokens: ', err.stack) + }) } + function deleteAccessToken(req, res) { const id = req.params.id dbapi.loadAccessToken(id).then(function(token) { @@ -567,9 +589,10 @@ function deleteAccessToken(req, res) { } }) .catch(function(err) { - apiutil.internalError(res, 'Failed to delete access token "%s": ', id, err.stack) - }) + apiutil.internalError(res, 'Failed to delete access token "%s": ', id, err.stack) + }) } + export {getUser} export {getUserDevices} export {addUserDevice} diff --git a/lib/units/api/controllers/users.js b/lib/units/api/controllers/users.js index f86d2b9147..30adb37af4 100644 --- a/lib/units/api/controllers/users.js +++ b/lib/units/api/controllers/users.js @@ -1,7 +1,7 @@ -import dbapi from '../../../db/api.mjs' +import * as dbapi from '../../../db/api.js' import _ from 'lodash' -import apiutil from '../../../util/apiutil.js' -import lockutil from '../../../util/lockutil.js' +import * as apiutil from '../../../util/apiutil.js' +import * as lockutil from '../../../util/lockutil.js' import Promise from 'bluebird' import wire from '../../../wire/index.js' import wireutil from '../../../wire/util.js' @@ -25,8 +25,8 @@ function userApiWrapper(fn, req, res) { } }) .catch(function(err) { - apiutil.internalError(res, 'Failed to wrap "%s": ', fn.name, err.stack) - }) + apiutil.internalError(res, 'Failed to wrap "%s": ', fn.name, err.stack) + }) } function getPublishedUser(user, userEmail, adminEmail, fields) { let publishedUser = apiutil.publishUser(user) @@ -54,8 +54,8 @@ function removeUser(email, req, res) { dbapi.removeGroupUser(id, email) }) .finally(function() { - lockutil.unlockGroup(lock) - }) + lockutil.unlockGroup(lock) + }) } function deleteUserInDatabase(channel) { return dbapi.removeUserAccessTokens(email).then(function() { @@ -79,14 +79,14 @@ function removeUser(email, req, res) { return !groupOwnerState || group.owner.email === email }) .then(function(results) { - return _.without(results, false).length > 0 - }) + return _.without(results, false).length > 0 + }) .catch(function(err) { - if (err === 'filtered') { - return false - } - throw err - }) + if (err === 'filtered') { + return false + } + throw err + }) } if (req.user.email === email) { return Promise.resolve('forbidden') @@ -105,14 +105,14 @@ function removeUser(email, req, res) { return removeGroupUser(group.owner.email, group.id) }) .then(function() { - return deleteUserInDatabase(user.group) - }) + return deleteUserInDatabase(user.group) + }) }) }) }) .finally(function() { - lockutil.unlockUser(lock) - }) + lockutil.unlockUser(lock) + }) } function grantAdmin(req, res) { if (req.user.privilege !== apiutil.ADMIN) { @@ -152,16 +152,16 @@ function updateUsersAlertMessage(req, res) { } }) .catch(function(err) { - apiutil.internalError(res, 'Failed to update users alert message: ', err.stack) - }) + apiutil.internalError(res, 'Failed to update users alert message: ', err.stack) + }) .catch(function(err) { - if (err !== 'busy') { - throw err - } - }) + if (err !== 'busy') { + throw err + } + }) .finally(function() { - lockutil.unlockUser(lock) - }) + lockutil.unlockUser(lock) + }) } /* --------------------------------- PUBLIC FUNCTIONS --------------------------------------- */ @@ -193,27 +193,27 @@ function getUsersAlertMessage(req, res) { throw new Error('Failed to initialize users alert message') }) .finally(function() { - lockutil.unlockUser(lock) - }) + lockutil.unlockUser(lock) + }) } return user?.settings?.alertMessage }) .then(function(alertMessage) { - if (fields) { - return _.pick(alertMessage, fields.split(',')) - } - else { - return alertMessage - } - }) + if (fields) { + return _.pick(alertMessage, fields.split(',')) + } + else { + return alertMessage + } + }) .then(function(alertMessage) { - apiutil.respond(res, 200, 'Users Alert Message', {alertMessage: alertMessage}) - }) + apiutil.respond(res, 200, 'Users Alert Message', {alertMessage: alertMessage}) + }) .catch(function(err) { - if (err !== 'busy') { - apiutil.internalError(res, 'Failed to get users alert message: ', err.stack) - } - }) + if (err !== 'busy') { + apiutil.internalError(res, 'Failed to get users alert message: ', err.stack) + } + }) } function updateUserGroupsQuotas(req, res) { const email = req.params.email @@ -231,27 +231,27 @@ function updateUserGroupsQuotas(req, res) { if (lockingSuccessed) { return dbapi.updateUserGroupsQuotas(email, duration, number, repetitions) .then(function(stats) { - if (stats.modifiedCount > 0) { - return apiutil.respond(res, 200, 'Updated (user quotas)', { - user: apiutil.publishUser(stats.changes[0].new_val) - }) - } - if ((duration === null || duration === lock.user.groups.quotas.allocated.duration) && + if (stats.modifiedCount > 0) { + return apiutil.respond(res, 200, 'Updated (user quotas)', { + user: apiutil.publishUser(stats.changes[0].new_val) + }) + } + if ((duration === null || duration === lock.user.groups.quotas.allocated.duration) && (number === null || number === lock.user.groups.quotas.allocated.number) && (repetitions === null || repetitions === lock.user.groups.quotas.repetitions)) { - return apiutil.respond(res, 200, 'Unchanged (user quotas)', {user: {}}) - } - return apiutil.respond(res, 400, 'Bad Request (quotas must be >= actual consumed resources)') - }) + return apiutil.respond(res, 200, 'Unchanged (user quotas)', {user: {}}) + } + return apiutil.respond(res, 400, 'Bad Request (quotas must be >= actual consumed resources)') + }) } return false }) .catch(function(err) { - apiutil.internalError(res, 'Failed to update user groups quotas: ', err.stack) - }) + apiutil.internalError(res, 'Failed to update user groups quotas: ', err.stack) + }) .finally(function() { - lockutil.unlockUser(lock) - }) + lockutil.unlockUser(lock) + }) } function updateDefaultUserGroupsQuotas(req, res) { const duration = typeof req.query.duration !== 'undefined' ? @@ -268,22 +268,22 @@ function updateDefaultUserGroupsQuotas(req, res) { if (lockingSuccessed) { return dbapi.updateDefaultUserGroupsQuotas(req.user.email, duration, number, repetitions) .then(function(stats) { - if (stats.modifiedCount > 0) { - return apiutil.respond(res, 200, 'Updated (user default quotas)', { - user: apiutil.publishUser(stats.changes[0].new_val) - }) - } - return apiutil.respond(res, 200, 'Unchanged (user default quotas)', {user: {}}) - }) + if (stats.modifiedCount > 0) { + return apiutil.respond(res, 200, 'Updated (user default quotas)', { + user: apiutil.publishUser(stats.changes[0].new_val) + }) + } + return apiutil.respond(res, 200, 'Unchanged (user default quotas)', {user: {}}) + }) } return false }) .catch(function(err) { - apiutil.internalError(res, 'Failed to update default user groups quotas: ', err.stack) - }) + apiutil.internalError(res, 'Failed to update default user groups quotas: ', err.stack) + }) .finally(function() { - lockutil.unlockUser(lock) - }) + lockutil.unlockUser(lock) + }) } function getUserByEmail(req, res) { const email = req.params.email @@ -296,8 +296,8 @@ function getUserByEmail(req, res) { } }) .catch(function(err) { - apiutil.internalError(res, 'Failed to get user: ', err.stack) - }) + apiutil.internalError(res, 'Failed to get user: ', err.stack) + }) } function getUsers(req, res) { const fields = req.query.fields @@ -311,8 +311,8 @@ function getUsers(req, res) { }) }) .catch(function(err) { - apiutil.internalError(res, 'Failed to get users: ', err.stack) - }) + apiutil.internalError(res, 'Failed to get users: ', err.stack) + }) } function createUser(req, res) { const email = req.params.email @@ -323,13 +323,13 @@ function createUser(req, res) { }) }) .catch(function(err) { - if (err instanceof MongoServerError && err.message.includes('duplicate key error collection')) { - return apiutil.respond(res, 400, 'Bad request (user already exists)') - } - else { - return apiutil.internalError(res, 'Failed to create user: ', err.stack) - } - }) + if (err instanceof MongoServerError && err.message.includes('duplicate key error collection')) { + return apiutil.respond(res, 400, 'Bad request (user already exists)') + } + else { + return apiutil.internalError(res, 'Failed to create user: ', err.stack) + } + }) } function createServiceUser(req, res) { if (req.user.privilege !== apiutil.ADMIN) { @@ -364,25 +364,25 @@ function deleteUsers(req, res) { }) }) .then(function() { - results = _.without(results, 'unchanged') - if (!results.length) { - return apiutil.respond(res, 200, `Unchanged (${target})`) - } - results = _.without(results, 'not found') - if (!results.length) { - return apiutil.respond(res, 404, `Not Found (${target})`) - } - results = _.without(results, 'forbidden') - if (!results.length) { - apiutil.respond(res, 403, `Forbidden (${target})`) - } - return apiutil.respond(res, 200, `Deleted (${target})`) - }) + results = _.without(results, 'unchanged') + if (!results.length) { + return apiutil.respond(res, 200, `Unchanged (${target})`) + } + results = _.without(results, 'not found') + if (!results.length) { + return apiutil.respond(res, 404, `Not Found (${target})`) + } + results = _.without(results, 'forbidden') + if (!results.length) { + apiutil.respond(res, 403, `Forbidden (${target})`) + } + return apiutil.respond(res, 200, `Deleted (${target})`) + }) .catch(function(err) { - if (err !== 'busy') { - throw err - } - }) + if (err !== 'busy') { + throw err + } + }) } (function() { if (typeof emails === 'undefined') { @@ -395,8 +395,8 @@ function deleteUsers(req, res) { } })() .catch(function(err) { - apiutil.internalError(res, 'Failed to delete ${target}: ', err.stack) - }) + apiutil.internalError(res, 'Failed to delete ${target}: ', err.stack) + }) } function deleteUser(req, res) { apiutil.redirectApiWrapper('email', deleteUsers, req, res) diff --git a/lib/units/api/helpers/ip-range-check/index.js b/lib/units/api/helpers/ip-range-check/index.js index 2869031421..0d7f4630be 100644 --- a/lib/units/api/helpers/ip-range-check/index.js +++ b/lib/units/api/helpers/ip-range-check/index.js @@ -28,8 +28,7 @@ function checkSingleCIDR(addr, cidr) { } } catch (e) { - // eslint-disable-next-line no-console - console.log(e) + console.error(e) return false } } diff --git a/lib/units/api/helpers/securityHandlers.js b/lib/units/api/helpers/securityHandlers.js index fff3505792..520e4d4f15 100644 --- a/lib/units/api/helpers/securityHandlers.js +++ b/lib/units/api/helpers/securityHandlers.js @@ -1,18 +1,11 @@ /* eslint-disable no-throw-literal */ -import dbapi from '../../../db/api.mjs' +import * as dbapi from '../../../db/api.js' import * as jwtutil from '../../../util/jwtutil.js' import logger from '../../../util/logger.js' -import apiutil from '../../../util/apiutil.js' +import * as apiutil from '../../../util/apiutil.js' const log = logger.createLogger('api:helpers:securityHandlers') // Specifications: https://tools.ietf.org/html/rfc6750#section-2.1 -async function accessTokenAuth(req, scopes, definition) { - if (process.env.MAINTENANCE_MODE === '1') { - throw { - status: 500 - , success: false - , description: 'Maintenance Mode. Please wait' - } - } +async function accessTokenAuth(req) { let operationTag if (req.operationDoc) { operationTag = req.operationDoc.tags @@ -20,41 +13,51 @@ async function accessTokenAuth(req, scopes, definition) { else { operationTag = 'not-swagger' } - if (req.headers.authorization) { - let authHeader = req.headers.authorization.split(' ') - let format = authHeader[0] - let tokenId = authHeader[1] - if (format !== 'Bearer') { - return { + if (req.headers.authorization || req.cookies.token) { + let format, tokenId, isCookie + if (req.headers.authorization) { + let authHeader = req.headers.authorization.split(' ') + format = authHeader[0] + tokenId = authHeader[1] + } + else { + tokenId = req.cookies.token + isCookie = true + } + if (format !== 'Bearer' && !isCookie) { + log.error('Invalid Header Format or missed cookie') + throw { status: 401 - , success: false - , description: 'Authorization header should be in "Bearer $AUTH_TOKEN" format' + , message: 'Authorization header should be in "Bearer $AUTH_TOKEN" format or in token cookie' } } if (!tokenId) { - log.error('Bad Access Token Header') + log.error('Bad Access Token Header or missed cookie') throw { status: 401 - , success: false - , description: 'Bad Credentials' + , message: 'Bad Credentials' } } try { - const token = await dbapi.loadAccessToken(tokenId) - if (!token) { - throw { - status: 401 - , success: false - , description: 'Bad Credentials' + let jwt + if (!isCookie) { + const token = await dbapi.loadAccessToken(tokenId) + if (!token) { + throw { + status: 401 + , message: 'Bad Credentials' + } } + jwt = token.jwt + } + else { + jwt = tokenId } - let jwt = token.jwt let data = jwtutil.decode(jwt, req.options.secret) if (!data) { throw { status: 401 - , success: false - , description: 'Cant decode JWT' + , message: 'Cant decode JWT' } } const user = await dbapi.loadUser(data.email) @@ -62,8 +65,7 @@ async function accessTokenAuth(req, scopes, definition) { if (user.privilege === apiutil.USER && operationTag.indexOf('admin') > -1) { throw { status: 403 - , success: false - , description: 'Forbidden: privileged operation (admin)' + , message: 'Forbidden: privileged operation (admin)' } } req.user = user @@ -73,14 +75,15 @@ async function accessTokenAuth(req, scopes, definition) { else { throw { status: 404 - , success: false - , description: 'User is not exist' + , message: 'User is not exist' } } } catch (err) { - log.error('Failed to load user or token: ', err) - throw err + throw { + status: err.status ?? 500 + , message: err.message ?? 'Internal server error' + } } } // Request is coming from browser app @@ -103,8 +106,7 @@ async function accessTokenAuth(req, scopes, definition) { else { throw { status: 500 - , success: false - , description: 'Internal Server Error' + , message: 'Internal error' } } } @@ -115,24 +117,21 @@ async function accessTokenAuth(req, scopes, definition) { if (format !== 'Internal') { throw { status: 401 - , success: false - , description: 'Authorization header should be in "Internal $AUTH_TOKEN" format' + , message: 'Authorization header should be in "Internal $AUTH_TOKEN" format' } } if (!tokenId) { log.error('Bad Access Token Header') throw { status: 401 - , success: false - , description: 'Bad Credentials' + , message: 'Bad Credentials' } } let data = jwtutil.decode(tokenId, req.options.secret) if (!data) { throw { status: 401 - , success: false - , description: 'Cant decode JWT' + , message: 'Bad JWT' } } try { @@ -141,8 +140,7 @@ async function accessTokenAuth(req, scopes, definition) { if (user.privilege === apiutil.USER && operationTag.indexOf('admin') > -1) { throw { status: 403 - , success: false - , description: 'Forbidden: privileged operation (admin)' + , message: 'Forbidden: privileged operation (admin)' } } req.user = user @@ -150,18 +148,16 @@ async function accessTokenAuth(req, scopes, definition) { } else { throw { - status: 404 - , success: false - , description: 'User is not exist' + status: 401 + , message: 'User is not exist' } } } catch (err) { - logger.error('Could. not load user', err) + log.error('Could. not load user', err) throw { status: 401 - , success: false - , description: 'Could not load user' + , message: 'Could not load user' } } } @@ -183,8 +179,7 @@ async function accessTokenAuth(req, scopes, definition) { else { throw { status: 401 - , success: false - , description: 'Requires Authentication' + , message: 'Requires Authentication' } } } diff --git a/lib/units/api/index.js b/lib/units/api/index.js index a87a08b59b..05522523a8 100644 --- a/lib/units/api/index.js +++ b/lib/units/api/index.js @@ -2,10 +2,10 @@ import http from 'http' import path from 'path' import events from 'events' import express from 'express' -// import * as swaggerExpress from 'swagger-express-mw-node12' +import cors from 'cors' import {initialize as expressInitialize} from 'express-openapi' -// import * as swaggerUi from 'swagger-tools/middleware/swagger-ui.js' import cookieSession from 'cookie-session' +import cookieParser from 'cookie-parser' import Promise from 'bluebird' import _ from 'lodash' import logger from '../../util/logger.js' @@ -16,9 +16,32 @@ import wireutil from '../../wire/util.js' import rateLimitConfig from '../ratelimit/index.js' import bodyParser from 'body-parser' import {accessTokenAuth} from './helpers/securityHandlers.js' -export default (function(options) { +export default (async function(options) { var log = logger.createLogger('api') var app = express() + try { + const Sentry = await import('@sentry/node') + + Sentry.setupExpressErrorHandler(app) + app.get('/debug-sentry', function mainHandler(req, res) { + Sentry.startSpan({ + op: 'test' + , name: 'My First Test Span', + }, () => { + try { + throw new Error('Span error.') + } + catch (e) { + Sentry.captureException(e) + } + }) + throw new Error('My first Sentry error!') + }) + } + catch { + log.error('Could not add sentry error handler') + } + var server = http.createServer(app) var channelRouter = new events.EventEmitter() channelRouter.setMaxListeners(100) @@ -33,9 +56,9 @@ export default (function(options) { }) }) .catch(function(err) { - log.fatal('Unable to connect to push endpoint', err) - lifecycle.fatal() - }) + log.fatal('Unable to connect to push endpoint', err) + lifecycle.fatal() + }) // Input var sub = zmqutil.socket('sub') Promise.map(options.endpoints.sub, function(endpoint) { @@ -48,9 +71,9 @@ export default (function(options) { }) }) .catch(function(err) { - log.fatal('Unable to connect to sub endpoint', err) - lifecycle.fatal() - }) + log.fatal('Unable to connect to sub endpoint', err) + lifecycle.fatal() + }) var pushdev = zmqutil.socket('push') Promise.map(options.endpoints.pushdev, function(endpoint) { return srv.resolve(endpoint).then(function(records) { @@ -62,9 +85,9 @@ export default (function(options) { }) }) .catch(function(err) { - log.fatal('Unable to connect to pushdev endpoint', err) - lifecycle.fatal() - }) + log.fatal('Unable to connect to pushdev endpoint', err) + lifecycle.fatal() + }) var subdev = zmqutil.socket('sub') Promise.map(options.endpoints.subdev, function(endpoint) { return srv.resolve(endpoint).then(function(records) { @@ -76,9 +99,9 @@ export default (function(options) { }) }) .catch(function(err) { - log.fatal('Unable to connect to subdev endpoint', err) - lifecycle.fatal() - }); + log.fatal('Unable to connect to subdev endpoint', err) + lifecycle.fatal() + }); [wireutil.global].forEach(function(channel) { log.info('Subscribing to permanent channel "%s"', channel) sub.subscribe(channel) @@ -92,34 +115,11 @@ export default (function(options) { }) // Swagger Express Config app.use(bodyParser.json()) - var config = { - app - , basePath: '/api/v1' - , appRoot: import.meta.dirname - , exposeApiDocs: true - , apiDoc: path.resolve(import.meta.dirname, 'swagger', 'api_v1.yaml') - , paths: path.resolve(import.meta.dirname, 'paths') - , securityHandlers: {accessTokenAuth} - , logger: { - debug: console.log - , info: console.log - , error: console.log - , warn: console.log - } - } - // swaggerExpress.create(config, function(err, swaggerExpress) { - // if (err) { - // throw err - // } - // app.use(swaggerUi(swaggerExpress.runner.swagger)) - // swaggerExpress.register(app) - // }) - expressInitialize(config) + app.use(cookieParser()) + app.use(rateLimitConfig) - // Adding options in request, so that swagger controller - // can use it. app.use(function(req, res, next) { - var reqOptions = _.merge(options, { + let reqOptions = _.merge(options, { push: push , sub: sub , channelRouter: channelRouter @@ -127,14 +127,37 @@ export default (function(options) { , subdev: subdev }) req.options = reqOptions - next() + accessTokenAuth(req) + .then(() => { + next() + }) + .catch(err => { + return res.status(err.status).json({ + message: err.message + }) + }) }) + let config = { + app + , basePath: '/api/v1' + , promiseMode: true + , appRoot: import.meta.dirname + , exposeApiDocs: true + , apiDoc: path.resolve(import.meta.dirname, 'swagger', 'api_v1.yaml') + , paths: path.resolve(import.meta.dirname, 'paths') + } + expressInitialize(config) + // TODO: Remove this once frontend is stateless app.use(cookieSession({ name: options.ssid , keys: [options.secret] })) - app.use(rateLimitConfig) + app.use(cors({ + origin: 'http://localhost:5173' + , credentials: true + , optionsSuccessStatus: 200 + })) lifecycle.observe(function() { [push, sub, pushdev, subdev].forEach(function(sock) { try { diff --git a/lib/units/api/paths/autotests.js b/lib/units/api/paths/autotests.js index b0de613590..7ed17fc14c 100644 --- a/lib/units/api/paths/autotests.js +++ b/lib/units/api/paths/autotests.js @@ -3,12 +3,12 @@ import {captureDevices, freeDevices} from '../controllers/autotests.js' export function get(req, res, next) { - return captureDevices(req, res, next) + return captureDevices(req, res, next) } export function del(req, res, next) { - return freeDevices(req, res, next) + return freeDevices(req, res, next) } diff --git a/lib/units/api/paths/autotests/install/{serial}.js b/lib/units/api/paths/autotests/install/{serial}.js index 2571be80b9..1b1e6c4bf9 100644 --- a/lib/units/api/paths/autotests/install/{serial}.js +++ b/lib/units/api/paths/autotests/install/{serial}.js @@ -3,7 +3,7 @@ import {installOnDevice} from '../../../controllers/autotests.js' export function post(req, res, next) { - return installOnDevice(req, res, next) + return installOnDevice(req, res, next) } diff --git a/lib/units/api/paths/autotests/useDevice.js b/lib/units/api/paths/autotests/useDevice.js index 0370e2c719..cf3f1d93b4 100644 --- a/lib/units/api/paths/autotests/useDevice.js +++ b/lib/units/api/paths/autotests/useDevice.js @@ -3,7 +3,7 @@ import {useAndConnectDevice} from '../../controllers/autotests.js' export function post(req, res, next) { - return useAndConnectDevice(req, res, next) + return useAndConnectDevice(req, res, next) } diff --git a/lib/units/api/paths/devices.js b/lib/units/api/paths/devices.js index 77ab7b6eed..c042d75346 100644 --- a/lib/units/api/paths/devices.js +++ b/lib/units/api/paths/devices.js @@ -3,12 +3,12 @@ import {getDevices, deleteDevices} from '../controllers/devices.js' export function get(req, res, next) { - return getDevices(req, res, next) + return getDevices(req, res, next) } export function del(req, res, next) { - return deleteDevices(req, res, next) + return deleteDevices(req, res, next) } diff --git a/lib/units/api/paths/devices/adbRange.js b/lib/units/api/paths/devices/adbRange.js index c76816433f..deebc9d9bb 100644 --- a/lib/units/api/paths/devices/adbRange.js +++ b/lib/units/api/paths/devices/adbRange.js @@ -3,7 +3,7 @@ import {getAdbRange} from '../../controllers/devices.js' export function get(req, res, next) { - return getAdbRange(req, res, next) + return getAdbRange(req, res, next) } diff --git a/lib/units/api/paths/devices/groups/{id}.js b/lib/units/api/paths/devices/groups/{id}.js index 5dce00bfd9..40fda5715e 100644 --- a/lib/units/api/paths/devices/groups/{id}.js +++ b/lib/units/api/paths/devices/groups/{id}.js @@ -3,12 +3,12 @@ import {addOriginGroupDevices, removeOriginGroupDevices} from '../../../controllers/devices.js' export function put(req, res, next) { - return addOriginGroupDevices(req, res, next) + return addOriginGroupDevices(req, res, next) } export function del(req, res, next) { - return removeOriginGroupDevices(req, res, next) + return removeOriginGroupDevices(req, res, next) } diff --git a/lib/units/api/paths/devices/{serial}.js b/lib/units/api/paths/devices/{serial}.js index aa2753bbb6..0df4f564d1 100644 --- a/lib/units/api/paths/devices/{serial}.js +++ b/lib/units/api/paths/devices/{serial}.js @@ -3,17 +3,17 @@ import {getDeviceBySerial, putDeviceBySerial, deleteDevice} from '../../controllers/devices.js' export function get(req, res, next) { - return getDeviceBySerial(req, res, next) + return getDeviceBySerial(req, res, next) } export function put(req, res, next) { - return putDeviceBySerial(req, res, next) + return putDeviceBySerial(req, res, next) } export function del(req, res, next) { - return deleteDevice(req, res, next) + return deleteDevice(req, res, next) } diff --git a/lib/units/api/paths/devices/{serial}/adbPort.js b/lib/units/api/paths/devices/{serial}/adbPort.js index c85d8fcda2..71ef9f76f4 100644 --- a/lib/units/api/paths/devices/{serial}/adbPort.js +++ b/lib/units/api/paths/devices/{serial}/adbPort.js @@ -3,7 +3,7 @@ import {renewAdbPort} from '../../../controllers/devices.js' export function put(req, res, next) { - return renewAdbPort(req, res, next) + return renewAdbPort(req, res, next) } diff --git a/lib/units/api/paths/devices/{serial}/bookings.js b/lib/units/api/paths/devices/{serial}/bookings.js index 5762858b82..90e7729549 100644 --- a/lib/units/api/paths/devices/{serial}/bookings.js +++ b/lib/units/api/paths/devices/{serial}/bookings.js @@ -3,7 +3,7 @@ import {getDeviceBookings} from '../../../controllers/devices.js' export function get(req, res, next) { - return getDeviceBookings(req, res, next) + return getDeviceBookings(req, res, next) } diff --git a/lib/units/api/paths/devices/{serial}/groups.js b/lib/units/api/paths/devices/{serial}/groups.js index bf44f1019d..9c804e9a6a 100644 --- a/lib/units/api/paths/devices/{serial}/groups.js +++ b/lib/units/api/paths/devices/{serial}/groups.js @@ -3,7 +3,7 @@ import {getDeviceGroups} from '../../../controllers/devices.js' export function get(req, res, next) { - return getDeviceGroups(req, res, next) + return getDeviceGroups(req, res, next) } diff --git a/lib/units/api/paths/devices/{serial}/groups/{id}.js b/lib/units/api/paths/devices/{serial}/groups/{id}.js index 8228908c96..6d7ac71ede 100644 --- a/lib/units/api/paths/devices/{serial}/groups/{id}.js +++ b/lib/units/api/paths/devices/{serial}/groups/{id}.js @@ -3,12 +3,12 @@ import {addOriginGroupDevice, removeOriginGroupDevice} from '../../../../controllers/devices.js' export function put(req, res, next) { - return addOriginGroupDevice(req, res, next) + return addOriginGroupDevice(req, res, next) } export function del(req, res, next) { - return removeOriginGroupDevice(req, res, next) + return removeOriginGroupDevice(req, res, next) } diff --git a/lib/units/api/paths/devices/{serial}/owner.js b/lib/units/api/paths/devices/{serial}/owner.js index dae6514b48..69a0ab2379 100644 --- a/lib/units/api/paths/devices/{serial}/owner.js +++ b/lib/units/api/paths/devices/{serial}/owner.js @@ -3,7 +3,7 @@ import {getDeviceOwner} from '../../../controllers/devices.js' export function get(req, res, next) { - return getDeviceOwner(req, res, next) + return getDeviceOwner(req, res, next) } diff --git a/lib/units/api/paths/devices/{serial}/size.js b/lib/units/api/paths/devices/{serial}/size.js index 1866fbf332..4eb2ae41cf 100644 --- a/lib/units/api/paths/devices/{serial}/size.js +++ b/lib/units/api/paths/devices/{serial}/size.js @@ -3,7 +3,7 @@ import {getDeviceSize} from '../../../controllers/devices.js' export function get(req, res, next) { - return getDeviceSize(req, res, next) + return getDeviceSize(req, res, next) } diff --git a/lib/units/api/paths/devices/{serial}/type.js b/lib/units/api/paths/devices/{serial}/type.js index f8cfbade74..db208ca0c7 100644 --- a/lib/units/api/paths/devices/{serial}/type.js +++ b/lib/units/api/paths/devices/{serial}/type.js @@ -3,7 +3,7 @@ import {getDeviceType} from '../../../controllers/devices.js' export function get(req, res, next) { - return getDeviceType(req, res, next) + return getDeviceType(req, res, next) } diff --git a/lib/units/api/paths/devices/{serial}/updateStorageInfo.js b/lib/units/api/paths/devices/{serial}/updateStorageInfo.js index 091e652dfe..00f2ac6ea9 100644 --- a/lib/units/api/paths/devices/{serial}/updateStorageInfo.js +++ b/lib/units/api/paths/devices/{serial}/updateStorageInfo.js @@ -3,7 +3,7 @@ import {updateStorageInfo} from '../../../controllers/devices.js' export function put(req, res, next) { - return updateStorageInfo(req, res, next) + return updateStorageInfo(req, res, next) } diff --git a/lib/units/api/paths/groups.js b/lib/units/api/paths/groups.js index c01be6d68b..eb05019660 100644 --- a/lib/units/api/paths/groups.js +++ b/lib/units/api/paths/groups.js @@ -3,17 +3,17 @@ import {getGroups, createGroup, deleteGroups} from '../controllers/groups.js' export function get(req, res, next) { - return getGroups(req, res, next) + return getGroups(req, res, next) } export function post(req, res, next) { - return createGroup(req, res, next) + return createGroup(req, res, next) } export function del(req, res, next) { - return deleteGroups(req, res, next) + return deleteGroups(req, res, next) } diff --git a/lib/units/api/paths/groups/{id}.js b/lib/units/api/paths/groups/{id}.js index 7d3a3cbbac..d66e0e7cb6 100644 --- a/lib/units/api/paths/groups/{id}.js +++ b/lib/units/api/paths/groups/{id}.js @@ -3,17 +3,17 @@ import {getGroup, updateGroup, deleteGroup} from '../../controllers/groups.js' export function get(req, res, next) { - return getGroup(req, res, next) + return getGroup(req, res, next) } export function put(req, res, next) { - return updateGroup(req, res, next) + return updateGroup(req, res, next) } export function del(req, res, next) { - return deleteGroup(req, res, next) + return deleteGroup(req, res, next) } diff --git a/lib/units/api/paths/groups/{id}/devices.js b/lib/units/api/paths/groups/{id}/devices.js index 658bd21891..7a9763a41c 100644 --- a/lib/units/api/paths/groups/{id}/devices.js +++ b/lib/units/api/paths/groups/{id}/devices.js @@ -3,17 +3,17 @@ import {getGroupDevices, addGroupDevices, removeGroupDevices} from '../../../controllers/groups.js' export function get(req, res, next) { - return getGroupDevices(req, res, next) + return getGroupDevices(req, res, next) } export function put(req, res, next) { - return addGroupDevices(req, res, next) + return addGroupDevices(req, res, next) } export function del(req, res, next) { - return removeGroupDevices(req, res, next) + return removeGroupDevices(req, res, next) } diff --git a/lib/units/api/paths/groups/{id}/devices/{serial}.js b/lib/units/api/paths/groups/{id}/devices/{serial}.js index ecadc1910f..0ec1ad94e7 100644 --- a/lib/units/api/paths/groups/{id}/devices/{serial}.js +++ b/lib/units/api/paths/groups/{id}/devices/{serial}.js @@ -3,17 +3,17 @@ import {getGroupDevice, addGroupDevice, removeGroupDevice} from '../../../../controllers/groups.js' export function get(req, res, next) { - return getGroupDevice(req, res, next) + return getGroupDevice(req, res, next) } export function put(req, res, next) { - return addGroupDevice(req, res, next) + return addGroupDevice(req, res, next) } export function del(req, res, next) { - return removeGroupDevice(req, res, next) + return removeGroupDevice(req, res, next) } diff --git a/lib/units/api/paths/groups/{id}/users.js b/lib/units/api/paths/groups/{id}/users.js index d0ecbd0e08..1949d486f1 100644 --- a/lib/units/api/paths/groups/{id}/users.js +++ b/lib/units/api/paths/groups/{id}/users.js @@ -3,17 +3,17 @@ import {getGroupUsers, addGroupUsers, removeGroupUsers} from '../../../controllers/groups.js' export function get(req, res, next) { - return getGroupUsers(req, res, next) + return getGroupUsers(req, res, next) } export function put(req, res, next) { - return addGroupUsers(req, res, next) + return addGroupUsers(req, res, next) } export function del(req, res, next) { - return removeGroupUsers(req, res, next) + return removeGroupUsers(req, res, next) } diff --git a/lib/units/api/paths/groups/{id}/users/{email}.js b/lib/units/api/paths/groups/{id}/users/{email}.js index 7f0b4e7da8..65593db0f2 100644 --- a/lib/units/api/paths/groups/{id}/users/{email}.js +++ b/lib/units/api/paths/groups/{id}/users/{email}.js @@ -3,17 +3,17 @@ import {getGroupUser, addGroupUser, removeGroupUser} from '../../../../controllers/groups.js' export function get(req, res, next) { - return getGroupUser(req, res, next) + return getGroupUser(req, res, next) } export function put(req, res, next) { - return addGroupUser(req, res, next) + return addGroupUser(req, res, next) } export function del(req, res, next) { - return removeGroupUser(req, res, next) + return removeGroupUser(req, res, next) } diff --git a/lib/units/api/paths/stats.js b/lib/units/api/paths/stats.js index a4c29f46b1..48dfd79674 100644 --- a/lib/units/api/paths/stats.js +++ b/lib/units/api/paths/stats.js @@ -3,7 +3,7 @@ import {writeStats} from '../controllers/stats.js' export function post(req, res, next) { - return writeStats(req, res, next) + return writeStats(req, res, next) } diff --git a/lib/units/api/paths/user.js b/lib/units/api/paths/user.js index aaa081be4b..b33764e735 100644 --- a/lib/units/api/paths/user.js +++ b/lib/units/api/paths/user.js @@ -3,7 +3,7 @@ import {getUser} from '../controllers/user.js' export function get(req, res, next) { - return getUser(req, res, next) + return getUser(req, res, next) } diff --git a/lib/units/api/paths/user/accessTokens.js b/lib/units/api/paths/user/accessTokens.js index 172114731b..843ddc2178 100644 --- a/lib/units/api/paths/user/accessTokens.js +++ b/lib/units/api/paths/user/accessTokens.js @@ -3,17 +3,17 @@ import {getUserAccessTokens, createAccessToken, deleteAccessTokens} from '../../controllers/user.js' export function get(req, res, next) { - return getUserAccessTokens(req, res, next) + return getUserAccessTokens(req, res, next) } export function post(req, res, next) { - return createAccessToken(req, res, next) + return createAccessToken(req, res, next) } export function del(req, res, next) { - return deleteAccessTokens(req, res, next) + return deleteAccessTokens(req, res, next) } diff --git a/lib/units/api/paths/user/accessTokens/{id}.js b/lib/units/api/paths/user/accessTokens/{id}.js index 2b0e2b70d0..66347b1cbc 100644 --- a/lib/units/api/paths/user/accessTokens/{id}.js +++ b/lib/units/api/paths/user/accessTokens/{id}.js @@ -3,12 +3,12 @@ import {getAccessToken, deleteAccessToken} from '../../../controllers/user.js' export function get(req, res, next) { - return getAccessToken(req, res, next) + return getAccessToken(req, res, next) } export function del(req, res, next) { - return deleteAccessToken(req, res, next) + return deleteAccessToken(req, res, next) } diff --git a/lib/units/api/paths/user/adbPublicKeys.js b/lib/units/api/paths/user/adbPublicKeys.js index b8ad32665f..c319abe8f0 100644 --- a/lib/units/api/paths/user/adbPublicKeys.js +++ b/lib/units/api/paths/user/adbPublicKeys.js @@ -3,12 +3,12 @@ import {addAdbPublicKey, removeAdbPublicKey} from '../../controllers/user.js' export function post(req, res, next) { - return addAdbPublicKey(req, res, next) + return addAdbPublicKey(req, res, next) } export function del(req, res, next) { - return removeAdbPublicKey(req, res, next) + return removeAdbPublicKey(req, res, next) } diff --git a/lib/units/api/paths/user/devices.js b/lib/units/api/paths/user/devices.js index 3eee53e5d1..86eff30a90 100644 --- a/lib/units/api/paths/user/devices.js +++ b/lib/units/api/paths/user/devices.js @@ -3,12 +3,12 @@ import {getUserDevices, addUserDevice} from '../../controllers/user.js' export function get(req, res, next) { - return getUserDevices(req, res, next) + return getUserDevices(req, res, next) } export function post(req, res, next) { - return addUserDevice(req, res, next) + return addUserDevice(req, res, next) } diff --git a/lib/units/api/paths/user/devices/{serial}.js b/lib/units/api/paths/user/devices/{serial}.js index 375977fbff..364dc0455a 100644 --- a/lib/units/api/paths/user/devices/{serial}.js +++ b/lib/units/api/paths/user/devices/{serial}.js @@ -3,17 +3,17 @@ import {getUserDeviceBySerial, addUserDeviceV2, deleteUserDeviceBySerial} from '../../../controllers/user.js' export function get(req, res, next) { - return getUserDeviceBySerial(req, res, next) + return getUserDeviceBySerial(req, res, next) } export function post(req, res, next) { - return addUserDeviceV2(req, res, next) + return addUserDeviceV2(req, res, next) } export function del(req, res, next) { - return deleteUserDeviceBySerial(req, res, next) + return deleteUserDeviceBySerial(req, res, next) } diff --git a/lib/units/api/paths/user/devices/{serial}/remoteConnect.js b/lib/units/api/paths/user/devices/{serial}/remoteConnect.js index 8197556fe5..68184d99a9 100644 --- a/lib/units/api/paths/user/devices/{serial}/remoteConnect.js +++ b/lib/units/api/paths/user/devices/{serial}/remoteConnect.js @@ -3,12 +3,12 @@ import {remoteConnectUserDeviceBySerial, remoteDisconnectUserDeviceBySerial} from '../../../../controllers/user.js' export function post(req, res, next) { - return remoteConnectUserDeviceBySerial(req, res, next) + return remoteConnectUserDeviceBySerial(req, res, next) } export function del(req, res, next) { - return remoteDisconnectUserDeviceBySerial(req, res, next) + return remoteDisconnectUserDeviceBySerial(req, res, next) } diff --git a/lib/units/api/paths/user/fullAccessTokens.js b/lib/units/api/paths/user/fullAccessTokens.js index 0a769c54b4..28287f1f98 100644 --- a/lib/units/api/paths/user/fullAccessTokens.js +++ b/lib/units/api/paths/user/fullAccessTokens.js @@ -3,7 +3,7 @@ import {getAccessTokens} from '../../controllers/user.js' export function get(req, res, next) { - return getAccessTokens(req, res, next) + return getAccessTokens(req, res, next) } diff --git a/lib/units/api/paths/users.js b/lib/units/api/paths/users.js index 56b6c29d71..8d1d9e7b49 100644 --- a/lib/units/api/paths/users.js +++ b/lib/units/api/paths/users.js @@ -3,12 +3,12 @@ import {getUsers, deleteUsers} from '../controllers/users.js' export function get(req, res, next) { - return getUsers(req, res, next) + return getUsers(req, res, next) } export function del(req, res, next) { - return deleteUsers(req, res, next) + return deleteUsers(req, res, next) } diff --git a/lib/units/api/paths/users/alertMessage.js b/lib/units/api/paths/users/alertMessage.js index 79d13fb9f6..33e43469fb 100644 --- a/lib/units/api/paths/users/alertMessage.js +++ b/lib/units/api/paths/users/alertMessage.js @@ -3,12 +3,12 @@ import {getUsersAlertMessage, updateUsersAlertMessage} from '../../controllers/users.js' export function get(req, res, next) { - return getUsersAlertMessage(req, res, next) + return getUsersAlertMessage(req, res, next) } export function put(req, res, next) { - return updateUsersAlertMessage(req, res, next) + return updateUsersAlertMessage(req, res, next) } diff --git a/lib/units/api/paths/users/grantAdmin/{email}.js b/lib/units/api/paths/users/grantAdmin/{email}.js index c7296127e5..7b0c81fc26 100644 --- a/lib/units/api/paths/users/grantAdmin/{email}.js +++ b/lib/units/api/paths/users/grantAdmin/{email}.js @@ -3,7 +3,7 @@ import {grantAdmin} from '../../../controllers/users.js' export function post(req, res, next) { - return grantAdmin(req, res, next) + return grantAdmin(req, res, next) } diff --git a/lib/units/api/paths/users/groupsQuotas.js b/lib/units/api/paths/users/groupsQuotas.js index 51438ebf4c..07a28b22eb 100644 --- a/lib/units/api/paths/users/groupsQuotas.js +++ b/lib/units/api/paths/users/groupsQuotas.js @@ -3,7 +3,7 @@ import {updateDefaultUserGroupsQuotas} from '../../controllers/users.js' export function put(req, res, next) { - return updateDefaultUserGroupsQuotas(req, res, next) + return updateDefaultUserGroupsQuotas(req, res, next) } diff --git a/lib/units/api/paths/users/revokeAdmin/{email}.js b/lib/units/api/paths/users/revokeAdmin/{email}.js index 149a866603..f7319eee5e 100644 --- a/lib/units/api/paths/users/revokeAdmin/{email}.js +++ b/lib/units/api/paths/users/revokeAdmin/{email}.js @@ -3,7 +3,7 @@ import {revokeAdmin} from '../../../controllers/users.js' export function del(req, res, next) { - return revokeAdmin(req, res, next) + return revokeAdmin(req, res, next) } diff --git a/lib/units/api/paths/users/service/{email}.js b/lib/units/api/paths/users/service/{email}.js index 219ed1e32b..20a337cc5f 100644 --- a/lib/units/api/paths/users/service/{email}.js +++ b/lib/units/api/paths/users/service/{email}.js @@ -3,7 +3,7 @@ import {createServiceUser} from '../../../controllers/users.js' export function post(req, res, next) { - return createServiceUser(req, res, next) + return createServiceUser(req, res, next) } diff --git a/lib/units/api/paths/users/{email}.js b/lib/units/api/paths/users/{email}.js index e59a597659..3a64192243 100644 --- a/lib/units/api/paths/users/{email}.js +++ b/lib/units/api/paths/users/{email}.js @@ -3,17 +3,17 @@ import {getUserByEmail, createUser, deleteUser} from '../../controllers/users.js' export function get(req, res, next) { - return getUserByEmail(req, res, next) + return getUserByEmail(req, res, next) } export function post(req, res, next) { - return createUser(req, res, next) + return createUser(req, res, next) } export function del(req, res, next) { - return deleteUser(req, res, next) + return deleteUser(req, res, next) } diff --git a/lib/units/api/paths/users/{email}/accessTokens.js b/lib/units/api/paths/users/{email}/accessTokens.js index fe5bc9773a..453f503995 100644 --- a/lib/units/api/paths/users/{email}/accessTokens.js +++ b/lib/units/api/paths/users/{email}/accessTokens.js @@ -3,17 +3,17 @@ import {getUserAccessTokensV2, createUserAccessToken, deleteUserAccessTokens} from '../../../controllers/users.js' export function get(req, res, next) { - return getUserAccessTokensV2(req, res, next) + return getUserAccessTokensV2(req, res, next) } export function post(req, res, next) { - return createUserAccessToken(req, res, next) + return createUserAccessToken(req, res, next) } export function del(req, res, next) { - return deleteUserAccessTokens(req, res, next) + return deleteUserAccessTokens(req, res, next) } diff --git a/lib/units/api/paths/users/{email}/accessTokens/{id}.js b/lib/units/api/paths/users/{email}/accessTokens/{id}.js index 23d4282cf9..556d8a233a 100644 --- a/lib/units/api/paths/users/{email}/accessTokens/{id}.js +++ b/lib/units/api/paths/users/{email}/accessTokens/{id}.js @@ -3,12 +3,12 @@ import {getUserAccessToken, deleteUserAccessToken} from '../../../../controllers/users.js' export function get(req, res, next) { - return getUserAccessToken(req, res, next) + return getUserAccessToken(req, res, next) } export function del(req, res, next) { - return deleteUserAccessToken(req, res, next) + return deleteUserAccessToken(req, res, next) } diff --git a/lib/units/api/paths/users/{email}/devices.js b/lib/units/api/paths/users/{email}/devices.js index a24ec99a4c..544b72ec52 100644 --- a/lib/units/api/paths/users/{email}/devices.js +++ b/lib/units/api/paths/users/{email}/devices.js @@ -3,7 +3,7 @@ import {getUserDevicesV2} from '../../../controllers/users.js' export function get(req, res, next) { - return getUserDevicesV2(req, res, next) + return getUserDevicesV2(req, res, next) } diff --git a/lib/units/api/paths/users/{email}/devices/{serial}.js b/lib/units/api/paths/users/{email}/devices/{serial}.js index 3b3e023771..23b3807d38 100644 --- a/lib/units/api/paths/users/{email}/devices/{serial}.js +++ b/lib/units/api/paths/users/{email}/devices/{serial}.js @@ -3,17 +3,17 @@ import {getUserDevice, addUserDeviceV3, deleteUserDevice} from '../../../../controllers/users.js' export function get(req, res, next) { - return getUserDevice(req, res, next) + return getUserDevice(req, res, next) } export function post(req, res, next) { - return addUserDeviceV3(req, res, next) + return addUserDeviceV3(req, res, next) } export function del(req, res, next) { - return deleteUserDevice(req, res, next) + return deleteUserDevice(req, res, next) } diff --git a/lib/units/api/paths/users/{email}/devices/{serial}/remoteConnect.js b/lib/units/api/paths/users/{email}/devices/{serial}/remoteConnect.js index 998776030f..9e4588d904 100644 --- a/lib/units/api/paths/users/{email}/devices/{serial}/remoteConnect.js +++ b/lib/units/api/paths/users/{email}/devices/{serial}/remoteConnect.js @@ -3,12 +3,12 @@ import {remoteConnectUserDevice, remoteDisconnectUserDevice} from '../../../../../controllers/users.js' export function post(req, res, next) { - return remoteConnectUserDevice(req, res, next) + return remoteConnectUserDevice(req, res, next) } export function del(req, res, next) { - return remoteDisconnectUserDevice(req, res, next) + return remoteDisconnectUserDevice(req, res, next) } diff --git a/lib/units/api/paths/users/{email}/groupsQuotas.js b/lib/units/api/paths/users/{email}/groupsQuotas.js index f08167afbf..95181c1c3a 100644 --- a/lib/units/api/paths/users/{email}/groupsQuotas.js +++ b/lib/units/api/paths/users/{email}/groupsQuotas.js @@ -3,7 +3,7 @@ import {updateUserGroupsQuotas} from '../../../controllers/users.js' export function put(req, res, next) { - return updateUserGroupsQuotas(req, res, next) + return updateUserGroupsQuotas(req, res, next) } diff --git a/lib/units/api/swagger/api_v1.yaml b/lib/units/api/swagger/api_v1.yaml index 5cf2572bb9..b0ac293454 100644 --- a/lib/units/api/swagger/api_v1.yaml +++ b/lib/units/api/swagger/api_v1.yaml @@ -10,7 +10,7 @@ info: license: name: Apache-2.0 url: http://www.apache.org/licenses/LICENSE-2.0 - version: 2.4.1 + version: 2.4.3 servers: - url: /api/v1 tags: @@ -64,8 +64,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] post: tags: - groups @@ -99,8 +97,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: group delete: tags: @@ -126,7 +122,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -139,8 +135,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: groups x-swagger-router-controller: groups /groups/{id}: @@ -180,8 +174,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] put: tags: - groups @@ -232,8 +224,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: group delete: tags: @@ -254,7 +244,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -267,8 +257,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: groups /groups/{id}/devices: get: @@ -317,8 +305,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] put: tags: - groups @@ -371,8 +357,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: devices delete: tags: @@ -419,8 +403,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: devices x-swagger-router-controller: groups /groups/{id}/devices/{serial}: @@ -466,8 +448,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] put: tags: - groups @@ -514,8 +494,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - groups @@ -555,8 +533,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: groups /groups/{id}/users: get: @@ -597,8 +573,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] put: tags: - groups @@ -643,8 +617,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: users delete: tags: @@ -690,8 +662,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: users x-swagger-router-controller: groups /groups/{id}/users/{email}: @@ -739,8 +709,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] put: tags: - groups @@ -779,8 +747,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - groups @@ -820,8 +786,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: groups /users: get: @@ -855,8 +819,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - admin @@ -888,7 +850,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -901,8 +863,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: users x-swagger-router-controller: users /users/alertMessage: @@ -936,8 +896,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] put: tags: - admin @@ -971,8 +929,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: alertMessage x-swagger-router-controller: users /users/groupsQuotas: @@ -1019,8 +975,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: users /users/grantAdmin/{email}: post: @@ -1054,8 +1008,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: users /users/revokeAdmin/{email}: delete: @@ -1089,8 +1041,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: users /users/{email}: get: @@ -1131,8 +1081,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] post: tags: - admin @@ -1169,8 +1117,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - admin @@ -1197,7 +1143,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -1210,8 +1156,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: users /users/service/{email}: post: @@ -1262,8 +1206,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: users /users/{email}/groupsQuotas: put: @@ -1317,8 +1259,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: users /users/{email}/devices: get: @@ -1357,8 +1297,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: users /users/{email}/devices/{serial}: get: @@ -1404,8 +1342,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] post: tags: - admin @@ -1441,7 +1377,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -1454,8 +1390,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - admin @@ -1483,7 +1417,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -1496,8 +1430,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: users /users/{email}/devices/{serial}/remoteConnect: post: @@ -1539,8 +1471,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - admin @@ -1567,7 +1497,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -1580,8 +1510,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: users /users/{email}/accessTokens: get: @@ -1614,8 +1542,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] post: tags: - admin @@ -1652,8 +1578,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - admin @@ -1673,7 +1597,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -1684,8 +1608,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: users /users/{email}/accessTokens/{id}: get: @@ -1724,8 +1646,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - admin @@ -1751,7 +1671,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -1762,8 +1682,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: users /user: get: @@ -1786,8 +1704,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: user /user/devices: get: @@ -1820,8 +1736,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] post: tags: - user @@ -1854,8 +1768,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: device x-swagger-router-controller: user /user/devices/{serial}: @@ -1896,8 +1808,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] post: tags: - user @@ -1927,7 +1837,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -1940,8 +1850,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - user @@ -1963,7 +1871,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -1976,8 +1884,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: user /user/devices/{serial}/remoteConnect: post: @@ -2013,8 +1919,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - user @@ -2035,7 +1939,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -2048,8 +1952,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: user /user/fullAccessTokens: get: @@ -2075,8 +1977,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: user /user/accessTokens: get: @@ -2099,8 +1999,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - security: - - accessTokenAuth: [] post: tags: - user @@ -2130,8 +2028,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - user @@ -2144,7 +2040,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -2154,8 +2050,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: user /user/accessTokens/{id}: get: @@ -2188,8 +2082,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - user @@ -2209,7 +2101,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -2220,8 +2112,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: user /user/adbPublicKeys: post: @@ -2255,8 +2145,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: adb delete: tags: @@ -2280,8 +2168,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: fingerprint x-swagger-router-controller: user /devices: @@ -2333,8 +2219,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - admin @@ -2388,7 +2272,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -2400,15 +2284,12 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: devices x-swagger-router-controller: devices /devices/adbRange: get: tags: - devices - - admin summary: AdbRange description: The range whic used to forward adb ports operationId: getAdbRange @@ -2428,8 +2309,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: devices /devices/{serial}: get: @@ -2464,8 +2343,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - security: - - accessTokenAuth: [] put: tags: - admin @@ -2493,7 +2370,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -2507,8 +2384,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: device delete: tags: @@ -2557,7 +2432,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -2569,8 +2444,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: devices /devices/{serial}/updateStorageInfo: put: @@ -2607,7 +2480,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -2621,8 +2494,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: devices /devices/{serial}/size: get: @@ -2711,8 +2582,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: devices delete: tags: @@ -2768,8 +2637,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: devices x-swagger-router-controller: devices /devices/{serial}/adbPort: @@ -2792,7 +2659,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Response' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -2806,8 +2673,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: devices /devices/{serial}/owner: get: @@ -2877,8 +2742,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: devices /devices/{serial}/type: get: @@ -2910,8 +2773,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: devices /devices/{serial}/bookings: get: @@ -2950,8 +2811,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: devices /devices/{serial}/groups/{id}: put: @@ -2995,8 +2854,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - admin @@ -3038,8 +2895,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: devices /autotests: get: @@ -3104,7 +2959,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/GroupListResponse' + $ref: '#/components/schemas/AutoTestResponse' default: description: | Unexpected Error: @@ -3115,8 +2970,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] delete: tags: - autotests @@ -3136,7 +2989,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/GroupListResponse' + $ref: '#/components/schemas/DefaultResponse' default: description: | Unexpected Error: @@ -3147,8 +3000,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: autotests /autotests/install/{serial}: post: @@ -3193,8 +3044,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: installProperties x-swagger-router-controller: autotests /autotests/useDevice: @@ -3226,7 +3075,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/GroupListResponse' + $ref: '#/components/schemas/RemoteConnectUserDeviceResponse' default: description: | Unexpected Error: @@ -3237,8 +3086,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-codegen-request-body-name: serial x-swagger-router-controller: autotests /stats: @@ -3294,8 +3141,6 @@ paths: application/json: schema: $ref: '#/components/schemas/UnexpectedErrorResponse' - security: - - accessTokenAuth: [] x-swagger-router-controller: stats /swagger.json: x-swagger-pipe: swagger_raw @@ -3312,7 +3157,7 @@ components: default: false description: type: string - Response: + DefaultResponse: required: - description - success @@ -3367,6 +3212,252 @@ components: name: type: string description: Owner of the group in conflict + Device: + type: object + properties: + _id: + type: string + present: + type: boolean + presenceChangedAt: + type: string + format: date-time + provider: + type: object + properties: + channel: + type: string + name: + type: string + owner: + type: object + properties: + email: + type: string + name: + type: string + group: + type: string + nullable: true + status: + type: integer + statusChangedAt: + type: string + format: date-time + bookedBefore: + type: integer + ready: + type: boolean + reverseForwards: + type: array + items: + type: object + properties: + id: + type: string + devicePort: + type: integer + minimum: 0 + targetHost: + type: string + targetPort: + type: integer + minimum: 0 + remoteConnect: + type: boolean + remoteConnectUrl: + type: string + nullable: true + usage: + type: string + nullable: true + logs_enabled: + type: boolean + serial: + type: string + createdAt: + type: string + format: date-time + group: + type: object + properties: + id: + type: string + name: + type: string + lifeTime: + type: object + properties: + start: + type: string + format: date-time + stop: + type: string + format: date-time + owner: + type: object + properties: + email: + type: string + name: + type: string + origin: + type: string + class: + type: string + repetitions: + type: integer + originName: + type: string + lock: + type: boolean + runUrl: + type: string + nullable: true + adbPort: + type: integer + network: + type: object + properties: + connected: + type: boolean + type: + type: string + subtype: + type: string + failover: + type: boolean + roaming: + type: boolean + display: + type: object + properties: + id: + type: integer + width: + type: integer + height: + type: integer + rotation: + type: integer + xdpi: + type: number + ydpi: + type: number + fps: + type: number + density: + type: number + secure: + type: boolean + url: + type: string + size: + type: number + airplaneMode: + type: boolean + battery: + type: object + properties: + status: + type: string + health: + type: string + source: + type: string + level: + type: integer + scale: + type: integer + temp: + type: integer + voltage: + type: integer + browser: + type: object + properties: + selected: + type: boolean + apps: + type: array + items: + type: object + properties: + id: + type: string + type: + type: string + name: + type: string + selected: + type: boolean + system: + type: boolean + service: + type: object + properties: + hasHMS: + type: boolean + hasGMS: + type: boolean + channel: + type: string + abi: + type: string + cpuPlatform: + type: string + macAddress: + type: string + manufacturer: + type: string + marketName: + type: string + model: + type: string + openGLESVersion: + type: string + operator: + type: string + phone: + type: object + properties: + imei: + type: string + imsi: + type: string + nullable: true + phoneNumber: + type: string + nullable: true + iccid: + type: string + nullable: true + network: + type: string + nullable: true + platform: + type: string + ios: + type: boolean + product: + type: string + ram: + type: string + sdk: + type: string + version: + type: string + usageChangedAt: + type: string + format: date-time + notes: + type: string + place: + type: string + storageId: + type: string + using: + type: boolean ConflictsResponse: required: - conflicts @@ -3402,6 +3493,26 @@ components: items: type: object properties: {} + AutoTestResponse: + required: + - description + - groups + - success + type: object + properties: + success: + type: boolean + description: + type: string + group: + type: object + properties: + devices: + type: array + items: + type: object + properties: {} + UserListResponse: required: - description @@ -3431,7 +3542,115 @@ components: type: string user: type: object - properties: {} + properties: + _id: + type: string + email: + type: string + name: + type: string + ip: + type: string + group: + type: string + lastLoggedInAt: + type: string + format: date-time + createdAt: + type: string + format: date-time + forwards: + type: array + items: {} + settings: + type: object + properties: + lastUsedDevice: + type: string + dateFormat: + type: string + emailAddressSeparator: + type: string + platform: + type: string + groupItemsPerPage: + type: object + properties: + name: + type: string + value: + type: integer + deviceListColumns: + type: array + items: + type: object + properties: + name: + type: string + selected: + type: boolean + selectedLanguage: + type: string + deviceListSort: + type: object + properties: + fixed: + type: array + items: + type: object + properties: + name: + type: string + order: + type: string + user: + type: array + items: + type: object + properties: + name: + type: string + order: + type: string + acceptedPolicy: + type: boolean + privilege: + type: string + groups: + type: object + properties: + subscribed: + type: array + items: + type: string + lock: + type: boolean + quotas: + type: object + properties: + allocated: + type: object + properties: + number: + type: number + duration: + type: number + consumed: + type: object + properties: + number: + type: number + duration: + type: number + defaultGroupsNumber: + type: number + defaultGroupsDuration: + type: number + defaultGroupsRepetitions: + type: number + repetitions: + type: integer + ServiceUserResponse: required: - description @@ -3555,8 +3774,7 @@ components: devices: type: array items: - type: object - properties: {} + $ref: '#/components/schemas/Device' SizeResponse: required: - height @@ -3596,8 +3814,7 @@ components: description: type: string device: - type: object - properties: {} + $ref: '#/components/schemas/Device' RemoteConnectUserDeviceResponse: required: - description @@ -3728,9 +3945,4 @@ components: url: type: string description: List of flags for adb install - securitySchemes: - accessTokenAuth: - type: apiKey - name: authorization - in: header x-original-swagger-version: "2.0" diff --git a/lib/units/app/index.js b/lib/units/app/index.js index 9e6fac119c..e20c0dd0cb 100644 --- a/lib/units/app/index.js +++ b/lib/units/app/index.js @@ -4,6 +4,7 @@ import fs from 'fs' import express from 'express' import validator from 'express-validator' import cookieSession from 'cookie-session' +import cookieParser from 'cookie-parser' import bodyParser from 'body-parser' import serveFavicon from 'serve-favicon' import serveStatic from 'serve-static' @@ -18,12 +19,22 @@ import appstoreIconMiddleware from './middleware/appstore-icons.js' import rateLimitConfig from '../ratelimit/index.js' import iconMiddleware from './middleware/icon.js' import * as markdownServe from 'markdown-serve' -import dbapi from '../../db/api.mjs' import webpackServer from '../../../webpack.config.cjs' import webpack from './middleware/webpack.js' -export default (function(options) { +export default (async function(options) { var log = logger.createLogger('app') var app = express() + try { + const Sentry = await import('@sentry/node') + + Sentry.setupExpressErrorHandler(app) + } + catch { + log.error('Could not add sentry error handler') + } + app.get('/debug-sentry', function mainHandler(req, res) { + throw new Error('My first Sentry error!') + }) var server = http.createServer(app) app.use('/static/wiki', markdownServe.middleware({ rootDirectory: pathutil.root('node_modules/@devicefarmer/stf-wiki') @@ -58,7 +69,12 @@ export default (function(options) { app.use('/static/app/icons', iconMiddleware(import.meta.dirname)) app.use('/static/app', serveStatic(pathutil.resource('app'))) app.use('/static/logo', serveStatic(pathutil.resource('common/logo'))) + app.use('/react', serveStatic(pathutil.reactFrontend('dist'))) + // TODO удалить после готовности реакт фронтенда + app.use('/assets', serveStatic(pathutil.reactFrontend('dist/assets'))) + app.use('/locales', serveStatic(pathutil.reactFrontend('dist/locales'))) app.use(serveFavicon(pathutil.resource('common/logo/exports/hub_favicon.png'))) + app.use(cookieParser()) app.use(cookieSession({ name: options.ssid , keys: [options.secret] @@ -75,6 +91,7 @@ export default (function(options) { res.send('OK') }) app.use(bodyParser.json()) + // TODO remove after react frontend app.use(csrf()) app.use(validator()) app.use(function(req, res, next) { @@ -85,12 +102,17 @@ export default (function(options) { app.get('/', function(req, res) { res.render('index') }) + app.get('/auth', function(req, res) { + res.redirect(options.authUrl) + }) app.get('/app/api/v1/auth_url', function(req, res) { res.send({ authUrl: options.authUrl }) }) - app.get('/app/api/v1/state.js', function(req, res) { + + // TODO удалить после готовности реакт фронтенда + const getGlobalAppState = (req) => { let state = { config: { websocketUrl: (function() { @@ -102,29 +124,16 @@ export default (function(options) { , user: req.user } if (options.userProfileUrl) { - state.config.userProfileUrl = (function() { - return options.userProfileUrl - })() + state.config.userProfileUrl = options.userProfileUrl } + return state + } + + app.get('/app/api/v1/state.js', function(req, res) { res.type('application/javascript') - res.send('var GLOBAL_APPSTATE = ' + JSON.stringify(state)) - }) - app.get('/app/api/v1/healthcheck', function(req, res) { - let state = { - readyDevices: [] - } - dbapi.getReadyDevices().then(devices => { - // eslint-disable-next-line no-undef - let SDKVersions = Array.from(new Set(devices.map(device => (device.sdk)))) - SDKVersions.forEach(version => { - if (version) { - state.readyDevices.push({sdk: version, count: devices.filter((device) => device.sdk === version).length}) - } - }) - state.readyDevicesCount = devices.length - res.send(state) - }) + res.send('var GLOBAL_APPSTATE = ' + JSON.stringify(getGlobalAppState(req))) }) + server.listen(options.port) log.info('Listening on port %d', options.port) }) diff --git a/lib/units/app/middleware/auth.js b/lib/units/app/middleware/auth.js index a5a0d8c303..6c275c3a77 100644 --- a/lib/units/app/middleware/auth.js +++ b/lib/units/app/middleware/auth.js @@ -1,15 +1,9 @@ import * as jwtutil from '../../../util/jwtutil.js' import * as urlutil from '../../../util/urlutil.js' -import dbapi from '../../../db/api.mjs' +import * as dbapi from '../../../db/api.js' import {accessTokenAuth} from '../../api/helpers/securityHandlers.js' export default (function(options) { return function(req, res, next) { - if (process.env.MAINTENANCE_MODE === '1') { - return res.status(500).json({ - success: false - , description: 'Maintenance Mode. Please wait' - }) - } if (req.query.jwt) { // Coming from auth client let data = jwtutil.decode(req.query.jwt, options.secret) @@ -22,17 +16,21 @@ export default (function(options) { , ip: req.ip }) .then(function() { - req.session.jwt = data - req.sessionOptions.httpOnly = false - dbapi.loadUser(data.email).then(user => { - if (user.acceptedPolicy) { - res.redirect(redir) - } - else { - res.redirect(redir + '?need_accept=1') - } + req.session.jwt = data + req.sessionOptions.httpOnly = false + dbapi.loadUser(data.email).then(user => { + if (user.acceptedPolicy) { + res.append('Set-Cookie', `token=${req.query.jwt}; HttpOnly`) + res.append('Access-Control-Allow-Credentials', 'true') + res.redirect(redir) + } + else { + res.append('Set-Cookie', `token=${req.query.jwt}; HttpOnly`) + res.append('Access-Control-Allow-Credentials', 'true') + res.redirect(redir + '?need_accept=1') + } + }) }) - }) .catch(next) } else { @@ -48,23 +46,26 @@ export default (function(options) { else if (req.session && req.session.jwt) { dbapi.loadUser(req.session.jwt.email) .then(function(user) { - if (user) { + if (user) { // Continue existing session - req.user = user - next() - } - else { + req.user = user + next() + } + else { // We no longer have the user in the database - res.redirect(options.authUrl) - } - }) + res.redirect(options.authUrl) + } + }) .catch(next) } - else if (req.headers.authorization) { // needed for /app/api/v1/ requests + else if (req.headers.authorization || req.cookies.token) { // needed for /app/api/v1/ requests req.options = { secret: options.secret } - accessTokenAuth(req, res, next) + accessTokenAuth(req).catch((err) => { + res.redirect(options.authUrl) + res.json(err) + }) } else { // No session, forward to auth client diff --git a/lib/units/app/middleware/webpack.js b/lib/units/app/middleware/webpack.js index 3108627b4a..0b086dcce2 100644 --- a/lib/units/app/middleware/webpack.js +++ b/lib/units/app/middleware/webpack.js @@ -71,16 +71,16 @@ export default (function(localOptions) { var target = path.join(compiler.outputPath, parsedUrl.pathname) bundle() .then(function() { - try { - let body = fs.readFileSync(target) - res.set('Content-Type', mime.lookup(target)) - res.set('Cache-Control', 'private, max-age=86400') - res.end(body) - } - catch (err) { - return next() - } - }) + try { + let body = fs.readFileSync(target) + res.set('Content-Type', mime.lookup(target)) + res.set('Cache-Control', 'private, max-age=86400') + res.end(body) + } + catch (err) { + return next() + } + }) .catch(next) } }) diff --git a/lib/units/auth/ldap.js b/lib/units/auth/ldap.js index bcf1fa78ff..ddc957dedb 100644 --- a/lib/units/auth/ldap.js +++ b/lib/units/auth/ldap.js @@ -1,5 +1,6 @@ import http from 'http' import express from 'express' +import cors from 'cors' import validator from 'express-validator' import cookieSession from 'cookie-session' import bodyParser from 'body-parser' @@ -23,7 +24,7 @@ export default (function(options) { return server.closeAsync() .catch(function() { // Okay - }) + }) }) app.set('view engine', 'pug') app.set('views', pathutil.resource('auth/ldap/views')) @@ -35,6 +36,11 @@ export default (function(options) { })) app.use(rateLimitConfig) app.use(bodyParser.json()) + app.use(cors({ + origin: 'http://localhost:5173' + , credentials: true + , optionsSuccessStatus: 200 + })) app.use(csrf()) app.use(validator()) app.use('/static/auth/ldap', serveStatic(pathutil.resource('auth/ldap'))) @@ -48,16 +54,16 @@ export default (function(options) { app.get('/auth/contact', function(req, res) { res.status(200) .json({ - success: true - , contactUrl: options.supportUrl - }) + success: true + , contactUrl: options.supportUrl + }) }) app.get('/auth/docs', function(req, res) { res.status(200) .json({ - success: true - , docsUrl: options.docsUrl - }) + success: true + , docsUrl: options.docsUrl + }) }) app.get('/auth/ldap/', function(req, res) { res.render('index') @@ -66,15 +72,15 @@ export default (function(options) { var log = logger.createLogger('auth-ldap') log.setLocalIdentifier(req.ip) switch (req.accepts(['json'])) { - case 'json': - requtil.validate(req, function() { - req.checkBody('username').notEmpty() - req.checkBody('password').notEmpty() - }) - .then(function() { + case 'json': + requtil.validate(req, function() { + req.checkBody('username').notEmpty() + req.checkBody('password').notEmpty() + }) + .then(function() { return ldaputil.login(options.ldap, req.body.username, req.body.password) }) - .then(function(user) { + .then(function(user) { log.info('Authenticated "%s"', ldaputil.email(user)) var token = jwtutil.encode({ payload: { @@ -88,40 +94,40 @@ export default (function(options) { }) res.status(200) .json({ - success: true - , redirect: urlutil.addParams(options.appUrl, { - jwt: token + success: true + , redirect: urlutil.addParams(options.appUrl, { + jwt: token + }) }) - }) }) - .catch(requtil.ValidationError, function(err) { + .catch(requtil.ValidationError, function(err) { res.status(400) .json({ - success: false - , error: 'ValidationError' - , validationErrors: err.errors - }) + success: false + , error: 'ValidationError' + , validationErrors: err.errors + }) }) - .catch(ldaputil.InvalidCredentialsError, function(err) { + .catch(ldaputil.InvalidCredentialsError, function(err) { log.warn('Authentication failure for "%s"', err.user) res.status(400) .json({ - success: false - , error: 'InvalidCredentialsError' - }) + success: false + , error: 'InvalidCredentialsError' + }) }) - .catch(function(err) { + .catch(function(err) { log.error('Unexpected error', err.stack) res.status(500) .json({ - success: false - , error: 'ServerError' - }) + success: false + , error: 'ServerError' + }) }) - break - default: - res.send(406) - break + break + default: + res.send(406) + break } }) server.listen(options.port) diff --git a/lib/units/auth/mock.js b/lib/units/auth/mock.js index 1c79638a63..de7c28797d 100644 --- a/lib/units/auth/mock.js +++ b/lib/units/auth/mock.js @@ -1,5 +1,6 @@ import http from 'http' import express from 'express' +import cors from 'cors' import validator from 'express-validator' import cookieSession from 'cookie-session' import bodyParser from 'body-parser' @@ -23,7 +24,7 @@ export default (function(options) { return server.closeAsync() .catch(function() { // Okay - }) + }) }) // BasicAuth Middleware var basicAuthMiddleware = function(req, res, next) { @@ -51,6 +52,11 @@ export default (function(options) { name: options.ssid , keys: [options.secret] })) + app.use(cors({ + origin: 'http://localhost:5173' + , credentials: true + , optionsSuccessStatus: 200 + })) app.use(bodyParser.json()) app.use(csrf()) app.use(validator()) @@ -69,16 +75,16 @@ export default (function(options) { app.get('/auth/contact', function(req, res) { res.status(200) .json({ - success: true - , contactUrl: options.supportUrl - }) + success: true + , contactUrl: options.supportUrl + }) }) app.get('/auth/docs', function(req, res) { res.status(200) .json({ - success: true - , docsUrl: options.docsUrl - }) + success: true + , docsUrl: options.docsUrl + }) }) app.get('/auth/mock/', function(req, res) { res.render('index') @@ -87,12 +93,12 @@ export default (function(options) { var log = logger.createLogger('auth-mock') log.setLocalIdentifier(req.ip) switch (req.accepts(['json'])) { - case 'json': - requtil.validate(req, function() { - req.checkBody('name').notEmpty() - req.checkBody('email').isEmail() - }) - .then(function() { + case 'json': + requtil.validate(req, function() { + req.checkBody('name').notEmpty() + req.checkBody('email').isEmail() + }) + .then(function() { log.info('Authenticated "%s"', req.body.email) log.info('options.appUrl "%s"', options.appUrl) var token = jwtutil.encode({ @@ -107,32 +113,32 @@ export default (function(options) { }) res.status(200) .json({ - success: true - , redirect: urlutil.addParams(options.appUrl, { - jwt: token + success: true + , redirect: urlutil.addParams(options.appUrl, { + jwt: token + }) }) - }) }) - .catch(requtil.ValidationError, function(err) { + .catch(requtil.ValidationError, function(err) { res.status(400) .json({ - success: false - , error: 'ValidationError' - , validationErrors: err.errors - }) + success: false + , error: 'ValidationError' + , validationErrors: err.errors + }) }) - .catch(function(err) { + .catch(function(err) { log.error('Unexpected error', err.stack) res.status(500) .json({ - success: false - , error: 'ServerError' - }) + success: false + , error: 'ServerError' + }) }) - break - default: - res.send(406) - break + break + default: + res.send(406) + break } }) server.listen(options.port) diff --git a/lib/units/auth/oauth2/index.js b/lib/units/auth/oauth2/index.js index 849499e55d..b630b9118f 100644 --- a/lib/units/auth/oauth2/index.js +++ b/lib/units/auth/oauth2/index.js @@ -1,12 +1,13 @@ import http from 'http' import express from 'express' +import cors from 'cors' import * as passport from 'passport' import logger from '../../../util/logger.js' import * as urlutil from '../../../util/urlutil.js' import * as jwtutil from '../../../util/jwtutil.js' import Strategy from './strategy.js' import rateLimitConfig from '../../ratelimit/index.js' -import dbapi from '../../../db/api.mjs' +import * as dbapi from '../../../db/api.js' export default (function(options) { var log = logger.createLogger('auth-oauth2') var app = express() @@ -17,23 +18,28 @@ export default (function(options) { dbapi.getRootGroup().then(function(group) { res.status(200) .json({ - success: true - , contact: group.owner - }) + success: true + , contact: group.owner + }) }) .catch(function(err) { - log.error('Unexpected error', err.stack) - res.status(500) - .json({ - success: false - , error: 'ServerError' + log.error('Unexpected error', err.stack) + res.status(500) + .json({ + success: false + , error: 'ServerError' + }) }) - }) }) function verify(accessToken, refreshToken, profile, done) { done(null, profile) } passport.use(new Strategy(options.oauth, verify)) + app.use(cors({ + origin: 'http://localhost:5173' + , credentials: true + , optionsSuccessStatus: 200 + })) app.use(rateLimitConfig) app.use(passport.initialize()) app.use(passport.authenticate('oauth2', { diff --git a/lib/units/auth/openid.js b/lib/units/auth/openid.js index 957f5b4116..1abfa8064b 100644 --- a/lib/units/auth/openid.js +++ b/lib/units/auth/openid.js @@ -1,6 +1,7 @@ import http from 'http' import openid from 'openid-client' import express from 'express' +import cors from 'cors' import cookieParser from 'cookie-parser' import bodyParser from 'body-parser' import logger from '../../util/logger.js' @@ -27,6 +28,11 @@ export default (function(options) { }) app.use(rateLimitConfig) app.use(cookieParser()) + app.use(cors({ + origin: 'http://localhost:5173' + , credentials: true + , optionsSuccessStatus: 200 + })) app.use(bodyParser.urlencoded({extended: false})) app.set('strict routing', true) app.set('case sensitive routing', true) @@ -45,6 +51,8 @@ export default (function(options) { }) res.cookie('openid-nonce', nonce, { httpOnly: true + , sameSite: 'none' + , secure: true }) res.redirect(url) } @@ -82,16 +90,16 @@ export default (function(options) { app.get('/auth/contact', function(req, res) { res.status(200) .json({ - success: true - , contactUrl: options.supportUrl - }) + success: true + , contactUrl: options.supportUrl + }) }) app.get('/auth/docs', function(req, res) { res.status(200) .json({ - success: true - , docsUrl: options.docsUrl - }) + success: true + , docsUrl: options.docsUrl + }) }) http.createServer(app).listen(options.port) log.info('Listening on port %d', options.port) diff --git a/lib/units/auth/saml2.js b/lib/units/auth/saml2.js index fd91db8cbc..ba7c1bc2b0 100644 --- a/lib/units/auth/saml2.js +++ b/lib/units/auth/saml2.js @@ -1,6 +1,7 @@ import fs from 'fs' import http from 'http' import express from 'express' +import cors from 'cors' import * as passport from 'passport' import * as passportSaml from 'passport-saml' import bodyParser from 'body-parser' @@ -9,7 +10,7 @@ import logger from '../../util/logger.js' import * as urlutil from '../../util/urlutil.js' import * as jwtutil from '../../util/jwtutil.js' import rateLimitConfig from '../ratelimit/index.js' -import dbapi from '../../db/api.mjs' +import * as dbapi from '../../db/api.js' var SamlStrategy = {Strategy: passportSaml}.Strategy export default (function(options) { var log = logger.createLogger('auth-saml2') @@ -18,6 +19,11 @@ export default (function(options) { app.set('strict routing', true) app.set('case sensitive routing', true) app.use(bodyParser.urlencoded({extended: false})) + app.use(cors({ + origin: 'http://localhost:5173' + , credentials: true + , optionsSuccessStatus: 200 + })) app.use(passport.initialize()) app.use(rateLimitConfig) passport.serializeUser(function(user, done) { @@ -82,25 +88,25 @@ export default (function(options) { dbapi.getRootGroup().then(function(group) { res.status(200) .json({ - success: true - , contact: group.owner - }) + success: true + , contact: group.owner + }) }) .catch(function(err) { - log.error('Unexpected error', err.stack) - res.status(500) - .json({ - success: false - , error: 'ServerError' + log.error('Unexpected error', err.stack) + res.status(500) + .json({ + success: false + , error: 'ServerError' + }) }) - }) }) app.get('/auth/docs', function(req, res) { res.status(200) .json({ - success: true - , docsUrl: options.docsUrl - }) + success: true + , docsUrl: options.docsUrl + }) }) server.listen(options.port) log.info('Listening on port %d', options.port) diff --git a/lib/units/base-device/support/channels.js b/lib/units/base-device/support/channels.js index 030953174f..a5fdd91d27 100644 --- a/lib/units/base-device/support/channels.js +++ b/lib/units/base-device/support/channels.js @@ -3,10 +3,10 @@ import logger from '../../../util/logger.js' import ChannelManager from '../../../wire/channelmanager.js' export default syrup.serial() .define(() => { - const log = logger.createLogger('device:support:channels') - let channels = new ChannelManager() - channels.on('timeout', channel => { - log.info('Channel "%s" timed out', channel) + const log = logger.createLogger('device:support:channels') + let channels = new ChannelManager() + channels.on('timeout', channel => { + log.info('Channel "%s" timed out', channel) + }) + return channels }) - return channels -}) diff --git a/lib/units/base-device/support/push.js b/lib/units/base-device/support/push.js index 8327b4aef9..1b131bfdde 100755 --- a/lib/units/base-device/support/push.js +++ b/lib/units/base-device/support/push.js @@ -6,21 +6,21 @@ import lifecycle from '../../../util/lifecycle.js' import * as zmqutil from '../../../util/zmqutil.js' export default syrup.serial() .define(options => { - const log = logger.createLogger('device:support:push') - // Output - let push = zmqutil.socket('push') - return Promise.map(options.endpoints.push, endpoint => { - return srv.resolve(endpoint).then(records => { - return srv.attempt(records, record => { - log.info('Device sending output to "%s"', record.url) - push.connect(record.url) - return Promise.resolve(true) + const log = logger.createLogger('device:support:push') + // Output + let push = zmqutil.socket('push') + return Promise.map(options.endpoints.push, endpoint => { + return srv.resolve(endpoint).then(records => { + return srv.attempt(records, record => { + log.info('Device sending output to "%s"', record.url) + push.connect(record.url) + return Promise.resolve(true) + }) }) }) + .catch(function(err) { + log.fatal('Unable to connect to sub endpoint', err) + lifecycle.fatal() + }) + .return(push) }) - .catch(function(err) { - log.fatal('Unable to connect to sub endpoint', err) - lifecycle.fatal() - }) - .return(push) -}) diff --git a/lib/units/base-device/support/router.js b/lib/units/base-device/support/router.js index 69c446d5cb..41406aba83 100755 --- a/lib/units/base-device/support/router.js +++ b/lib/units/base-device/support/router.js @@ -6,11 +6,11 @@ export default syrup.serial() .dependency(sub) .dependency(channels) .define((options, sub, channels) => { - let router = wirerouter() - sub.on('message', router.handler()) - // Special case, we're hooking into a message that's not actually routed. - router.on({$code: 'message'}, channel => { - channels.keepalive(channel) + let router = wirerouter() + sub.on('message', router.handler()) + // Special case, we're hooking into a message that's not actually routed. + router.on({$code: 'message'}, channel => { + channels.keepalive(channel) + }) + return router }) - return router -}) diff --git a/lib/units/base-device/support/sub.js b/lib/units/base-device/support/sub.js index 1cdd9ba58a..f82bc77926 100644 --- a/lib/units/base-device/support/sub.js +++ b/lib/units/base-device/support/sub.js @@ -7,28 +7,28 @@ import lifecycle from '../../../util/lifecycle.js' import * as zmqutil from '../../../util/zmqutil.js' export default syrup.serial() .define((options) => { - const log = logger.createLogger('device:support:sub') - // Input - let sub = zmqutil.socket('sub') - return Promise.map(options.endpoints.sub, endpoint => { - return srv.resolve(endpoint).then(records => { - return srv.attempt(records, record => { - log.info('Receiving input from "%s"', record.url) - sub.connect(record.url) - return Promise.resolve(true) + const log = logger.createLogger('device:support:sub') + // Input + let sub = zmqutil.socket('sub') + return Promise.map(options.endpoints.sub, endpoint => { + return srv.resolve(endpoint).then(records => { + return srv.attempt(records, record => { + log.info('Receiving input from "%s"', record.url) + sub.connect(record.url) + return Promise.resolve(true) + }) }) }) + .then(() => { + // Establish always-on channels + [wireutil.global].forEach(channel => { + log.info('Subscribing to permanent channel "%s"', channel) + sub.subscribe(channel) + }) + }) + .catch(function(err) { + log.fatal('Unable to connect to sub endpoint', err) + lifecycle.fatal(err) + }) + .return(sub) }) - .then(() => { - // Establish always-on channels - [wireutil.global].forEach(channel => { - log.info('Subscribing to permanent channel "%s"', channel) - sub.subscribe(channel) - }) - }) - .catch(function(err) { - log.fatal('Unable to connect to sub endpoint', err) - lifecycle.fatal(err) - }) - .return(sub) -}) diff --git a/lib/units/device/index.js b/lib/units/device/index.js index fe1a1b2759..5622ff1dba 100644 --- a/lib/units/device/index.js +++ b/lib/units/device/index.js @@ -36,52 +36,52 @@ export default (function(options) { // We want to send logs before anything else starts happening .dependency(logger$0) .define(function(options) { - const log = logger.createLogger('device') - log.info('Preparing device') - return syrup.serial() - .dependency(heartbeat) - .dependency(solo) - .dependency(stream) - .dependency(capture) + const log = logger.createLogger('device') + log.info('Preparing device') + return syrup.serial() + .dependency(heartbeat) + .dependency(solo) + .dependency(stream) + .dependency(capture) // .dependency(require('./plugins/vnc')) - .dependency(service) - .dependency(browser) - .dependency(store) - .dependency(clipboard) - .dependency(logcat) - .dependency(mute) - .dependency(shell) - .dependency(touch) - .dependency(install) - .dependency(forward) - .dependency(group) - .dependency(cleanup) - .dependency(reboot) - .dependency(connect) - .dependency(account) - .dependency(ringer) - .dependency(wifi) - .dependency(bluetooth) - .dependency(sd) - .dependency(filesystem) - .dependency(mobileService) - .dependency(remotedebug) - .define(function(options, heartbeat, solo) { - if (process.send) { - // Only if we have a parent process - process.send('ready') - } - log.info('Fully operational') - return solo.poke() + .dependency(service) + .dependency(browser) + .dependency(store) + .dependency(clipboard) + .dependency(logcat) + .dependency(mute) + .dependency(shell) + .dependency(touch) + .dependency(install) + .dependency(forward) + .dependency(group) + .dependency(cleanup) + .dependency(reboot) + .dependency(connect) + .dependency(account) + .dependency(ringer) + .dependency(wifi) + .dependency(bluetooth) + .dependency(sd) + .dependency(filesystem) + .dependency(mobileService) + .dependency(remotedebug) + .define(function(options, heartbeat, solo) { + if (process.send) { + // Only if we have a parent process + process.send('ready') + } + log.info('Fully operational') + return solo.poke() + }) + .consume(options) }) - .consume(options) - }) .consume(options) .catch(function(err) { - log.fatal('Setup had an error', err.stack) - if (err.stack.includes('no service started')) { - return lifecycle.graceful(err.stack) - } - lifecycle.fatal() - }) + log.fatal('Setup had an error', err.stack) + if (err.stack.includes('no service started')) { + return lifecycle.graceful(err.stack) + } + lifecycle.fatal() + }) }) diff --git a/lib/units/device/plugins/account.js b/lib/units/device/plugins/account.js index 7ff41232e5..b6fa1a9362 100644 --- a/lib/units/device/plugins/account.js +++ b/lib/units/device/plugins/account.js @@ -16,296 +16,296 @@ export default syrup.serial() .dependency(push) .dependency(adb) .define(function(options, service, identity, touch, router, push, adb) { - var log = logger.createLogger('device:plugins:account') - function checkAccount(type, account) { - return service.getAccounts({type: type}) - .timeout(30000) - .then(function(accounts) { - if (accounts.indexOf(account) >= 0) { - return true - } - throw new Error('The account is not added') - }) - } - router.on(wire.AccountCheckMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - log.info('Checking if account "%s" is added', message.account) - checkAccount(message.type, message.account) - .then(function() { - push.send([ - channel - , reply.okay() - ]) - }) - .catch(function(err) { - log.error('Account check failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) - }) - }) - router.on(wire.AccountGetMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - log.info('Getting account(s)') - service.getAccounts(message) - .timeout(30000) - .then(function(accounts) { - push.send([ - channel - , reply.okay('success', accounts) - ]) - }) - .catch(function(err) { - log.error('Account get failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) - }) - }) - router.on(wire.AccountRemoveMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - log.info('Removing "%s" account(s)', message.type) - service.removeAccount(message) - .timeout(30000) - .then(function() { - push.send([ - channel - , reply.okay() - ]) + var log = logger.createLogger('device:plugins:account') + function checkAccount(type, account) { + return service.getAccounts({type: type}) + .timeout(30000) + .then(function(accounts) { + if (accounts.indexOf(account) >= 0) { + return true + } + throw new Error('The account is not added') + }) + } + router.on(wire.AccountCheckMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + log.info('Checking if account "%s" is added', message.account) + checkAccount(message.type, message.account) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(function(err) { + log.error('Account check failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) - .catch(function(err) { - log.error('Account removal failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) + router.on(wire.AccountGetMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + log.info('Getting account(s)') + service.getAccounts(message) + .timeout(30000) + .then(function(accounts) { + push.send([ + channel + , reply.okay('success', accounts) + ]) + }) + .catch(function(err) { + log.error('Account get failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) - }) - router.on(wire.AccountAddMenuMessage, function(channel) { - var reply = wireutil.reply(options.serial) - log.info('Showing add account menu for Google Account') - service.addAccountMenu() - .timeout(30000) - .then(function() { - push.send([ - channel - , reply.okay() - ]) + router.on(wire.AccountRemoveMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + log.info('Removing "%s" account(s)', message.type) + service.removeAccount(message) + .timeout(30000) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(function(err) { + log.error('Account removal failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) - .catch(function(err) { - log.error('Add account menu failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) + router.on(wire.AccountAddMenuMessage, function(channel) { + var reply = wireutil.reply(options.serial) + log.info('Showing add account menu for Google Account') + service.addAccountMenu() + .timeout(30000) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(function(err) { + log.error('Add account menu failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) - }) - router.on(wire.AccountAddMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - var type = 'com.google' - var account = message.user + '@gmail.com' - log.info('Adding Google Account automatedly') - var version = identity.version.substring(0, 3) - function automation() { - switch (version) { + router.on(wire.AccountAddMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + var type = 'com.google' + var account = message.user + '@gmail.com' + log.info('Adding Google Account automatedly') + var version = identity.version.substring(0, 3) + function automation() { + switch (version) { case '2.3': // tested: 2.3.3-2.3.6 return service.pressKey('dpad_down').delay(1000) .then(function() { - return service.pressKey('dpad_down') - }).delay(1000) + return service.pressKey('dpad_down') + }).delay(1000) .then(function() { - return service.pressKey('enter') - }).delay(2000) + return service.pressKey('enter') + }).delay(2000) .then(function() { - return service.pressKey('dpad_down') - }).delay(2000) + return service.pressKey('dpad_down') + }).delay(2000) .then(function() { - return service.pressKey('enter') - }).delay(2000) + return service.pressKey('enter') + }).delay(2000) .then(function() { - return service.type(message.user) - }).delay(1000) + return service.type(message.user) + }).delay(1000) .then(function() { - return service.pressKey('switch_charset') - }).delay(100) + return service.pressKey('switch_charset') + }).delay(100) .then(function() { - return service.pressKey('switch_charset') - }).delay(100) + return service.pressKey('switch_charset') + }).delay(100) .then(function() { - return service.pressKey('enter') - }).delay(1000) + return service.pressKey('enter') + }).delay(1000) .then(function() { - return service.type(message.password) - }).delay(1000) + return service.type(message.password) + }).delay(1000) .then(function() { - return service.pressKey('enter') - }) + return service.pressKey('enter') + }) case '4.0': // tested: 4.0.3 and 4.0.4 return service.pressKey('tab').delay(1000) .then(function() { - return service.pressKey('enter') - }).delay(2000) + return service.pressKey('enter') + }).delay(2000) .then(function() { - return service.type(message.user) - }).delay(1000) + return service.type(message.user) + }).delay(1000) .then(function() { - return service.pressKey('switch_charset') - }).delay(100) + return service.pressKey('switch_charset') + }).delay(100) .then(function() { - return service.pressKey('switch_charset') - }).delay(100) + return service.pressKey('switch_charset') + }).delay(100) .then(function() { - return service.pressKey('enter') - }).delay(1000) + return service.pressKey('enter') + }).delay(1000) .then(function() { - return service.type(message.password) - }).delay(1000) + return service.type(message.password) + }).delay(1000) .then(function() { - return service.pressKey('enter') - }) + return service.pressKey('enter') + }) case '4.1': // tested: 4.1.1 and 4.1.2 return service.pressKey('tab').delay(1000) .then(function() { - return service.pressKey('enter') - }).delay(2000) + return service.pressKey('enter') + }).delay(2000) .then(function() { - return service.type(message.user) - }).delay(1000) + return service.type(message.user) + }).delay(1000) .then(function() { - return service.pressKey('switch_charset') - }).delay(100) + return service.pressKey('switch_charset') + }).delay(100) .then(function() { - return service.pressKey('switch_charset') - }).delay(100) + return service.pressKey('switch_charset') + }).delay(100) .then(function() { - return service.pressKey('enter') - }).delay(1000) + return service.pressKey('enter') + }).delay(1000) .then(function() { - return service.type(message.password) - }).delay(1000) + return service.type(message.password) + }).delay(1000) .then(function() { - return service.pressKey('enter') - }) + return service.pressKey('enter') + }) case '4.2': // tested: 4.2.2 return service.pressKey('tab').delay(1000) .then(function() { - return service.pressKey('enter') - }).delay(2000) + return service.pressKey('enter') + }).delay(2000) .then(function() { - return service.type(message.user) - }).delay(1000) + return service.type(message.user) + }).delay(1000) .then(function() { - return service.pressKey('switch_charset') - }).delay(100) + return service.pressKey('switch_charset') + }).delay(100) .then(function() { - return service.pressKey('switch_charset') - }).delay(100) + return service.pressKey('switch_charset') + }).delay(100) .then(function() { - return service.pressKey('enter') - }).delay(1000) + return service.pressKey('enter') + }).delay(1000) .then(function() { - return service.type(message.password) - }).delay(1000) + return service.type(message.password) + }).delay(1000) .then(function() { - return service.pressKey('enter') - }).delay(1000) + return service.pressKey('enter') + }).delay(1000) .then(function() { - return service.pressKey('tab') - }).delay(1000) + return service.pressKey('tab') + }).delay(1000) .then(function() { - return service.pressKey('tab') - }).delay(1000) + return service.pressKey('tab') + }).delay(1000) .then(function() { - return service.pressKey('tab') - }).delay(1000) + return service.pressKey('tab') + }).delay(1000) .then(function() { - return service.pressKey('enter') - }) + return service.pressKey('enter') + }) // case '4.3': // tested: 4.3 // case '4.4': // tested: 4.4.2 default: return service.pressKey('tab').delay(1000) .then(function() { - return service.pressKey('enter') - }).delay(2000) + return service.pressKey('enter') + }).delay(2000) .then(function() { - return service.type(message.user) - }).delay(1000) + return service.type(message.user) + }).delay(1000) .then(function() { - return service.pressKey('switch_charset') - }).delay(100) + return service.pressKey('switch_charset') + }).delay(100) .then(function() { - return service.pressKey('switch_charset') - }).delay(100) + return service.pressKey('switch_charset') + }).delay(100) .then(function() { - return service.pressKey('enter') - }).delay(1000) + return service.pressKey('enter') + }).delay(1000) .then(function() { - return service.type(message.password) - }).delay(1000) + return service.type(message.password) + }).delay(1000) .then(function() { - return service.pressKey('enter') - }).delay(1000) + return service.pressKey('enter') + }).delay(1000) .then(function() { - return service.pressKey('tab') - }).delay(1000) + return service.pressKey('tab') + }).delay(1000) .then(function() { - return service.pressKey('tab') - }).delay(1000) + return service.pressKey('tab') + }).delay(1000) .then(function() { - return service.pressKey('enter') - }) + return service.pressKey('enter') + }) + } } - } - // First check if the account is already added so we don't continue - return checkAccount(type, account) - .then(function() { - push.send([ - channel - , reply.fail('Add account failed: account was already added') - ]) - }) - .catch(function() { - return adb.getDevice(options.serial).clear('com.google.android.gsf.login') - .catch(function() { - // The package name is different in 2.3, so let's try the old name - // if the new name fails. - return adb.getDevice(options.serial).clear('com.google.android.gsf') - }) + // First check if the account is already added so we don't continue + return checkAccount(type, account) .then(function() { - return service.addAccountMenu() - }) - .delay(5000) - .then(function() { - // Just in case the add account menu has any button focused - return touch.tap({x: 0, y: 0.9}) - }) - .delay(500) - .then(function() { - return automation() - }) - .delay(3000) - .then(function() { - return service.pressKey('home') - }) - .then(function() { - return checkAccount(type, account) - }) - .then(function() { - push.send([ - channel - , reply.okay() - ]) - }) - .catch(function(err) { - log.error('Add account failed', err.stack) - push.send([ - channel - , reply.fail('Add account failed: ' + err.message) - ]) - }) + push.send([ + channel + , reply.fail('Add account failed: account was already added') + ]) + }) + .catch(function() { + return adb.getDevice(options.serial).clear('com.google.android.gsf.login') + .catch(function() { + // The package name is different in 2.3, so let's try the old name + // if the new name fails. + return adb.getDevice(options.serial).clear('com.google.android.gsf') + }) + .then(function() { + return service.addAccountMenu() + }) + .delay(5000) + .then(function() { + // Just in case the add account menu has any button focused + return touch.tap({x: 0, y: 0.9}) + }) + .delay(500) + .then(function() { + return automation() + }) + .delay(3000) + .then(function() { + return service.pressKey('home') + }) + .then(function() { + return checkAccount(type, account) + }) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(function(err) { + log.error('Add account failed', err.stack) + push.send([ + channel + , reply.fail('Add account failed: ' + err.message) + ]) + }) + }) }) }) -}) diff --git a/lib/units/device/plugins/bluetooth.js b/lib/units/device/plugins/bluetooth.js index b175c57606..9b04476e5b 100644 --- a/lib/units/device/plugins/bluetooth.js +++ b/lib/units/device/plugins/bluetooth.js @@ -10,62 +10,62 @@ export default syrup.serial() .dependency(router) .dependency(push) .define(function(options, service, router, push) { - var log = logger.createLogger('device:plugins:bluetooth') - router.on(wire.BluetoothSetEnabledMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - log.info('Setting Bluetooth "%s"', message.enabled) - service.setBluetoothEnabled(message.enabled) - .timeout(30000) - .then(function() { - push.send([ - channel - , reply.okay() - ]) + var log = logger.createLogger('device:plugins:bluetooth') + router.on(wire.BluetoothSetEnabledMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + log.info('Setting Bluetooth "%s"', message.enabled) + service.setBluetoothEnabled(message.enabled) + .timeout(30000) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(function(err) { + log.error('Setting Bluetooth enabled failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) - .catch(function(err) { - log.error('Setting Bluetooth enabled failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) + router.on(wire.BluetoothGetStatusMessage, function(channel) { + var reply = wireutil.reply(options.serial) + log.info('Getting Bluetooth status') + service.getBluetoothStatus() + .timeout(30000) + .then(function(enabled) { + push.send([ + channel + , reply.okay(enabled ? 'bluetooth_enabled' : 'bluetooth_disabled') + ]) + }) + .catch(function(err) { + log.error('Getting Bluetooth status failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) - }) - router.on(wire.BluetoothGetStatusMessage, function(channel) { - var reply = wireutil.reply(options.serial) - log.info('Getting Bluetooth status') - service.getBluetoothStatus() - .timeout(30000) - .then(function(enabled) { - push.send([ - channel - , reply.okay(enabled ? 'bluetooth_enabled' : 'bluetooth_disabled') - ]) - }) - .catch(function(err) { - log.error('Getting Bluetooth status failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) - }) - }) - router.on(wire.BluetoothCleanBondedMessage, function(channel) { - var reply = wireutil.reply(options.serial) - log.info('Clean bonded Bluetooth devices') - service.cleanupBondedBluetoothDevices() - .timeout(30000) - .then(function() { - push.send([ - channel - , reply.okay() - ]) - }) - .catch(function(err) { - log.error('Cleaning Bluetooth bonded devices failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) + router.on(wire.BluetoothCleanBondedMessage, function(channel) { + var reply = wireutil.reply(options.serial) + log.info('Clean bonded Bluetooth devices') + service.cleanupBondedBluetoothDevices() + .timeout(30000) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(function(err) { + log.error('Cleaning Bluetooth bonded devices failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) }) -}) diff --git a/lib/units/device/plugins/browser.js b/lib/units/device/plugins/browser.js index 9f90bab5f6..9e070e6794 100644 --- a/lib/units/device/plugins/browser.js +++ b/lib/units/device/plugins/browser.js @@ -23,109 +23,109 @@ export default syrup.serial() .dependency(adb) .dependency(service) .define(function(options, router, push, adb, service) { - var log = logger.createLogger('device:plugins:browser') - function pkg(component) { - return component.split('/', 1)[0] - } - function appReducer(acc, app) { - var packageName = pkg(app.component) - var browserId = mapping[packageName] - if (!browserId) { - log.warn('Unmapped browser "%s"', packageName) + var log = logger.createLogger('device:plugins:browser') + function pkg(component) { + return component.split('/', 1)[0] + } + function appReducer(acc, app) { + var packageName = pkg(app.component) + var browserId = mapping[packageName] + if (!browserId) { + log.warn('Unmapped browser "%s"', packageName) + return acc + } + acc.push({ + id: app.component + , type: browserId + , name: browsers[browserId].name + , selected: app.selected + , system: app.system + }) return acc } - acc.push({ - id: app.component - , type: browserId - , name: browsers[browserId].name - , selected: app.selected - , system: app.system - }) - return acc - } - function compareIgnoreCase(a, b) { - var la = (a || '').toLowerCase() - var lb = (b || '').toLowerCase() - if (la === lb) { - return 0 + function compareIgnoreCase(a, b) { + var la = (a || '').toLowerCase() + var lb = (b || '').toLowerCase() + if (la === lb) { + return 0 + } + else if (la < lb) { + return -1 + } + else { + return 1 + } } - else if (la < lb) { - return -1 + function updateBrowsers(data) { + log.info('Updating browser list') + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceBrowserMessage(options.serial, data.selected, data.apps.reduce(appReducer, []).sort(function(appA, appB) { + return compareIgnoreCase(appA.name, appB.name) + }))) + ]) } - else { - return 1 + function loadBrowsers() { + log.info('Loading browser list') + return service.getBrowsers() + .then(updateBrowsers) } - } - function updateBrowsers(data) { - log.info('Updating browser list') - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceBrowserMessage(options.serial, data.selected, data.apps.reduce(appReducer, []).sort(function(appA, appB) { - return compareIgnoreCase(appA.name, appB.name) - }))) - ]) - } - function loadBrowsers() { - log.info('Loading browser list') - return service.getBrowsers() - .then(updateBrowsers) - } - function ensureHttpProtocol(url) { + function ensureHttpProtocol(url) { // Check for '://' because a protocol-less URL might include // a username:password combination. - return (url.indexOf('://') === -1 ? 'http://' : '') + url - } - service.on('browserPackageChange', updateBrowsers) - router.on(wire.BrowserOpenMessage, function(channel, message) { - message.url = ensureHttpProtocol(message.url) - if (message.browser) { - log.info('Opening "%s" in "%s"', message.url, message.browser) + return (url.indexOf('://') === -1 ? 'http://' : '') + url } - else { - log.info('Opening "%s"', message.url) - } - var reply = wireutil.reply(options.serial) - adb.getDevice(options.serial).startActivity({ - action: 'android.intent.action.VIEW' - , component: message.browser - , data: message.url - }) - .then(function() { - push.send([ - channel - , reply.okay() - ]) - }) - .catch(function(err) { + service.on('browserPackageChange', updateBrowsers) + router.on(wire.BrowserOpenMessage, function(channel, message) { + message.url = ensureHttpProtocol(message.url) if (message.browser) { - log.error('Failed to open "%s" in "%s"', message.url, message.browser, err.stack) + log.info('Opening "%s" in "%s"', message.url, message.browser) } else { - log.error('Failed to open "%s"', message.url, err.stack) + log.info('Opening "%s"', message.url) } - push.send([ - channel - , reply.fail() - ]) + var reply = wireutil.reply(options.serial) + adb.getDevice(options.serial).startActivity({ + action: 'android.intent.action.VIEW' + , component: message.browser + , data: message.url + }) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(function(err) { + if (message.browser) { + log.error('Failed to open "%s" in "%s"', message.url, message.browser, err.stack) + } + else { + log.error('Failed to open "%s"', message.url, err.stack) + } + push.send([ + channel + , reply.fail() + ]) + }) }) - }) - router.on(wire.BrowserClearMessage, function(channel, message) { - log.info('Clearing "%s"', message.browser) - var reply = wireutil.reply(options.serial) - adb.getDevice(options.serial).clear(pkg(message.browser)) - .then(function() { - push.send([ - channel - , reply.okay() - ]) - }) - .catch(function(err) { - log.error('Failed to clear "%s"', message.browser, err.stack) - push.send([ - channel - , reply.fail() - ]) + router.on(wire.BrowserClearMessage, function(channel, message) { + log.info('Clearing "%s"', message.browser) + var reply = wireutil.reply(options.serial) + adb.getDevice(options.serial).clear(pkg(message.browser)) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(function(err) { + log.error('Failed to clear "%s"', message.browser, err.stack) + push.send([ + channel + , reply.fail() + ]) + }) }) + return loadBrowsers() }) - return loadBrowsers() -}) diff --git a/lib/units/device/plugins/cleanup.js b/lib/units/device/plugins/cleanup.js index 99d057b7a9..940f6721f4 100644 --- a/lib/units/device/plugins/cleanup.js +++ b/lib/units/device/plugins/cleanup.js @@ -12,58 +12,58 @@ export default syrup.serial() .dependency(group) .dependency(service$0) .define(function(options, adb, stfservice, group, service) { - var log = logger.createLogger('device:plugins:cleanup') - var plugin = Object.create(null) - if (!options.cleanup) { - return plugin - } - function listPackages() { - return adb.getDevice(options.serial).getPackages() - } - function uninstallPackage(pkg) { - log.info('Cleaning up package "%s"', pkg) - return adb.getDevice(options.serial).uninstall(pkg) - .catch(function(err) { - log.warn('Unable to clean up package "%s"', pkg, err) - return true - }) - } - return listPackages() - .then(function(initialPackages) { - initialPackages.push(stfservice.pkg) - plugin.removePackages = function() { - return listPackages() - .then(function(currentPackages) { - var remove = _.difference(currentPackages, initialPackages) - return Promise.map(remove, uninstallPackage) - }) + var log = logger.createLogger('device:plugins:cleanup') + var plugin = Object.create(null) + if (!options.cleanup) { + return plugin } - plugin.disableBluetooth = function() { - if (!options.cleanupDisableBluetooth) { - return - } - return service.getBluetoothStatus() - .then(function(enabled) { - if (enabled) { - log.info('Disabling Bluetooth') - return service.setBluetoothEnabled(false) - } - }) + function listPackages() { + return adb.getDevice(options.serial).getPackages() } - plugin.cleanBluetoothBonds = function() { - if (!options.cleanupBluetoothBonds) { - return - } - log.info('Cleanup Bluetooth bonds') - return service.cleanBluetoothBonds() + function uninstallPackage(pkg) { + log.info('Cleaning up package "%s"', pkg) + return adb.getDevice(options.serial).uninstall(pkg) + .catch(function(err) { + log.warn('Unable to clean up package "%s"', pkg, err) + return true + }) } - group.on('leave', function() { - Promise.all([ - plugin.removePackages() - , plugin.cleanBluetoothBonds() - , plugin.disableBluetooth() - ]) - }) + return listPackages() + .then(function(initialPackages) { + initialPackages.push(stfservice.pkg) + plugin.removePackages = function() { + return listPackages() + .then(function(currentPackages) { + var remove = _.difference(currentPackages, initialPackages) + return Promise.map(remove, uninstallPackage) + }) + } + plugin.disableBluetooth = function() { + if (!options.cleanupDisableBluetooth) { + return + } + return service.getBluetoothStatus() + .then(function(enabled) { + if (enabled) { + log.info('Disabling Bluetooth') + return service.setBluetoothEnabled(false) + } + }) + } + plugin.cleanBluetoothBonds = function() { + if (!options.cleanupBluetoothBonds) { + return + } + log.info('Cleanup Bluetooth bonds') + return service.cleanBluetoothBonds() + } + group.on('leave', function() { + Promise.all([ + plugin.removePackages() + , plugin.cleanBluetoothBonds() + , plugin.disableBluetooth() + ]) + }) + }) + .return(plugin) }) - .return(plugin) -}) diff --git a/lib/units/device/plugins/clipboard.js b/lib/units/device/plugins/clipboard.js index fba5891faa..71b7285e89 100644 --- a/lib/units/device/plugins/clipboard.js +++ b/lib/units/device/plugins/clipboard.js @@ -10,41 +10,41 @@ export default syrup.serial() .dependency(push) .dependency(service) .define(function(options, router, push, service) { - var log = logger.createLogger('device:plugins:clipboard') - router.on(wire.PasteMessage, function(channel, message) { - log.info('Pasting "%s" to clipboard', message.text) - var reply = wireutil.reply(options.serial) - service.paste(message.text) - .then(function() { - push.send([ - channel - , reply.okay() - ]) + var log = logger.createLogger('device:plugins:clipboard') + router.on(wire.PasteMessage, function(channel, message) { + log.info('Pasting "%s" to clipboard', message.text) + var reply = wireutil.reply(options.serial) + service.paste(message.text) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(function(err) { + log.error('Paste failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) - .catch(function(err) { - log.error('Paste failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) + router.on(wire.CopyMessage, function(channel) { + log.info('Copying clipboard contents') + var reply = wireutil.reply(options.serial) + service.copy() + .then(function(content) { + push.send([ + channel + , reply.okay(content) + ]) + }) + .catch(function(err) { + log.error('Copy failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) }) - router.on(wire.CopyMessage, function(channel) { - log.info('Copying clipboard contents') - var reply = wireutil.reply(options.serial) - service.copy() - .then(function(content) { - push.send([ - channel - , reply.okay(content) - ]) - }) - .catch(function(err) { - log.error('Copy failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) - }) - }) -}) diff --git a/lib/units/device/plugins/connect.js b/lib/units/device/plugins/connect.js index 4d297347dd..272da0dcb7 100644 --- a/lib/units/device/plugins/connect.js +++ b/lib/units/device/plugins/connect.js @@ -6,7 +6,7 @@ import * as grouputil from '../../../util/grouputil.js' import wire from '../../../wire/index.js' import wireutil from '../../../wire/util.js' import lifecycle from '../../../util/lifecycle.js' -import dbapi from '../../../db/api.mjs' +import * as dbapi from '../../../db/api.js' import adb from '../support/adb.js' import router from '../../base-device/support/router.js' import push from '../../base-device/support/push.js' @@ -21,185 +21,185 @@ export default syrup.serial() .dependency(solo) .dependency(urlformat) .define(function(options, adb, router, push, group, solo, urlformat) { - var log = logger.createLogger('device:plugins:connect') - var plugin = Object.create(null) - var activeServer = null - plugin.port = options.connectPort - plugin.url = urlformat(options.connectUrlPattern, plugin.port) - plugin.start = function() { - return new Promise(function(resolve, reject) { - if (plugin.isRunning()) { - return resolve(plugin.url) - } - var server = adb.createTcpUsbBridge(options.serial, { - auth: function(key) { - var resolver = Promise.defer() - function notify() { - group.get() - .then(function(currentGroup) { - push.send([ - solo.channel - , wireutil.envelope(new wire.JoinGroupByAdbFingerprintMessage(options.serial, key.fingerprint, key.comment, currentGroup.group)) - ]) - }) - .catch(grouputil.NoGroupError, function() { - push.send([ - solo.channel - , wireutil.envelope(new wire.JoinGroupByAdbFingerprintMessage(options.serial, key.fingerprint, key.comment)) - ]) - }) - } - function joinListener(group, identifier) { - if (identifier !== key.fingerprint) { - resolver.reject(new Error('Somebody else took the device')) + var log = logger.createLogger('device:plugins:connect') + var plugin = Object.create(null) + var activeServer = null + plugin.port = options.connectPort + plugin.url = urlformat(options.connectUrlPattern, plugin.port) + plugin.start = function() { + return new Promise(function(resolve, reject) { + if (plugin.isRunning()) { + return resolve(plugin.url) + } + var server = adb.createTcpUsbBridge(options.serial, { + auth: function(key) { + var resolver = Promise.defer() + function notify() { + group.get() + .then(function(currentGroup) { + push.send([ + solo.channel + , wireutil.envelope(new wire.JoinGroupByAdbFingerprintMessage(options.serial, key.fingerprint, key.comment, currentGroup.group)) + ]) + }) + .catch(grouputil.NoGroupError, function() { + push.send([ + solo.channel + , wireutil.envelope(new wire.JoinGroupByAdbFingerprintMessage(options.serial, key.fingerprint, key.comment)) + ]) + }) } - } - function autojoinListener(identifier, joined) { - if (identifier === key.fingerprint) { - if (joined) { - resolver.resolve() + function joinListener(group, identifier) { + if (identifier !== key.fingerprint) { + resolver.reject(new Error('Somebody else took the device')) } - else { - resolver.reject(new Error('Device is already in use')) + } + function autojoinListener(identifier, joined) { + if (identifier === key.fingerprint) { + if (joined) { + resolver.resolve() + } + else { + resolver.reject(new Error('Device is already in use')) + } } } + group.on('join', joinListener) + group.on('autojoin', autojoinListener) + router.on(wire.AdbKeysUpdatedMessage, notify) + notify() + return resolver.promise + .timeout(120000) + .finally(function() { + group.removeListener('join', joinListener) + group.removeListener('autojoin', autojoinListener) + router.removeListener(wire.AdbKeysUpdatedMessage, notify) + }) } - group.on('join', joinListener) - group.on('autojoin', autojoinListener) - router.on(wire.AdbKeysUpdatedMessage, notify) - notify() - return resolver.promise - .timeout(120000) - .finally(function() { - group.removeListener('join', joinListener) - group.removeListener('autojoin', autojoinListener) - router.removeListener(wire.AdbKeysUpdatedMessage, notify) + }) + server.on('listening', function() { + resolve(plugin.url) + }) + server.on('connection', function(conn) { + log.info('New remote ADB connection from %s', conn.remoteAddress) + conn.on('userActivity', function() { + group.keepalive() }) - } - }) - server.on('listening', function() { - resolve(plugin.url) - }) - server.on('connection', function(conn) { - log.info('New remote ADB connection from %s', conn.remoteAddress) - conn.on('userActivity', function() { - group.keepalive() }) + server.on('error', reject) + log.info(util.format('Listening on port %d', plugin.port)) + server.listen(plugin.port) + activeServer = server + lifecycle.share('Remote ADB', activeServer) }) - server.on('error', reject) - log.info(util.format('Listening on port %d', plugin.port)) - server.listen(plugin.port) - activeServer = server - lifecycle.share('Remote ADB', activeServer) - }) - } - plugin.stop = Promise.method(function() { - if (plugin.isRunning()) { - activeServer.close() - activeServer.end() - activeServer = null } - }) - plugin.end = Promise.method(function() { - if (plugin.isRunning()) { - activeServer.end() + plugin.stop = Promise.method(function() { + if (plugin.isRunning()) { + activeServer.close() + activeServer.end() + activeServer = null + } + }) + plugin.end = Promise.method(function() { + if (plugin.isRunning()) { + activeServer.end() + } + }) + plugin.isRunning = function() { + return !!activeServer } - }) - plugin.isRunning = function() { - return !!activeServer - } - lifecycle.observe(plugin.stop) - group.on('leave', plugin.stop) - router - .on(wire.ConnectStartMessage, function(channel) { - let reply = wireutil.reply(options.serial) - plugin.start() - .then(function(directUrl) { - dbapi.loadDeviceBySerial(options.serial).then((device) =>{ - let baseUrl = options.storageUrl.split('/')[2] - baseUrl = baseUrl.split(':') - let url - if (device.adbPort) { - url = baseUrl[0] + ':' + device.adbPort.toString() - } - else { - if (options.urlWithoutAdbPort) { - url = directUrl - } - else { - url = 'unavailable. Contact administrator' - } - } + lifecycle.observe(plugin.stop) + group.on('leave', plugin.stop) + router + .on(wire.ConnectStartMessage, function(channel) { + let reply = wireutil.reply(options.serial) + plugin.start() + .then(function(directUrl) { + dbapi.loadDeviceBySerial(options.serial).then((device) =>{ + let baseUrl = options.storageUrl.split('/')[2] + baseUrl = baseUrl.split(':') + let url + if (device.adbPort) { + url = baseUrl[0] + ':' + device.adbPort.toString() + } + else { + if (options.urlWithoutAdbPort) { + url = directUrl + } + else { + url = 'unavailable. Contact administrator' + } + } - push.send([ - channel - , reply.okay(url) - ]) + push.send([ + channel + , reply.okay(url) + ]) - // Update DB - push.send([ - channel - , wireutil.envelope(new wire.ConnectStartedMessage( - options.serial - , url - )) - ]) - log.important('Remote Connect Started for device "%s" at "%s"', options.serial, url) + // Update DB + push.send([ + channel + , wireutil.envelope(new wire.ConnectStartedMessage( + options.serial + , url + )) + ]) + log.important('Remote Connect Started for device "%s" at "%s"', options.serial, url) + }) + }) + .catch(function(err) { + log.error('Unable to start remote connect service', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) - }) - .catch(function(err) { - log.error('Unable to start remote connect service', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) - }) - }) - .on(wire.ConnectGetForwardUrlMessage, function(channel) { - let reply = wireutil.reply(options.serial) - plugin.start() - .then(function(url) { - push.send([ - channel - , reply.okay(url) - ]) - // Update DB - push.send([ - channel - , wireutil.envelope(new wire.ConnectStartedMessage(options.serial, url)) - ]) - log.important('Remote Connect Started for device "%s" at "%s"', options.serial, url) - }) - .catch(function(err) { - log.error('Unable to start remote connect service', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) - }) - }) - .on(wire.ConnectStopMessage, function(channel) { - var reply = wireutil.reply(options.serial) - plugin.stop() - .then(function() { - push.send([ - channel - , reply.okay() - ]) - // Update DB - push.send([ - channel - , wireutil.envelope(new wire.ConnectStoppedMessage(options.serial)) - ]) - log.important('Remote Connect Stopped for device "%s"', options.serial) - }) - .catch(function(err) { - log.error('Failed to stop connect service', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) - }) + .on(wire.ConnectGetForwardUrlMessage, function(channel) { + let reply = wireutil.reply(options.serial) + plugin.start() + .then(function(url) { + push.send([ + channel + , reply.okay(url) + ]) + // Update DB + push.send([ + channel + , wireutil.envelope(new wire.ConnectStartedMessage(options.serial, url)) + ]) + log.important('Remote Connect Started for device "%s" at "%s"', options.serial, url) + }) + .catch(function(err) { + log.error('Unable to start remote connect service', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) + }) + .on(wire.ConnectStopMessage, function(channel) { + var reply = wireutil.reply(options.serial) + plugin.stop() + .then(function() { + push.send([ + channel + , reply.okay() + ]) + // Update DB + push.send([ + channel + , wireutil.envelope(new wire.ConnectStoppedMessage(options.serial)) + ]) + log.important('Remote Connect Stopped for device "%s"', options.serial) + }) + .catch(function(err) { + log.error('Failed to stop connect service', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) + }) + return (plugin) }) - return (plugin) -}) diff --git a/lib/units/device/plugins/filesystem.js b/lib/units/device/plugins/filesystem.js index f3012cfe49..8744945e72 100644 --- a/lib/units/device/plugins/filesystem.js +++ b/lib/units/device/plugins/filesystem.js @@ -13,59 +13,59 @@ export default syrup.serial() .dependency(push) .dependency(storage) .define(function(options, adb, router, push, storage) { - var log = logger.createLogger('device:plugins:filesystem') - var plugin = Object.create(null) - plugin.retrieve = function(file) { - log.info('Retrieving file "%s"', file) - return adb.getDevice(options.serial).stat(file) - .then(function(stats) { - return adb.getDevice(options.serial).pull(file) - .then(function(transfer) { - // We may have add new storage plugins for various file types - // in the future, and add proper detection for the mimetype. - // But for now, let's just use application/octet-stream for - // everything like it's 2001. - return storage.store('blob', transfer, { - filename: path.basename(file) - , contentType: 'application/octet-stream' - , knownLength: stats.size + var log = logger.createLogger('device:plugins:filesystem') + var plugin = Object.create(null) + plugin.retrieve = function(file) { + log.info('Retrieving file "%s"', file) + return adb.getDevice(options.serial).stat(file) + .then(function(stats) { + return adb.getDevice(options.serial).pull(file) + .then(function(transfer) { + // We may have add new storage plugins for various file types + // in the future, and add proper detection for the mimetype. + // But for now, let's just use application/octet-stream for + // everything like it's 2001. + return storage.store('blob', transfer, { + filename: path.basename(file) + , contentType: 'application/octet-stream' + , knownLength: stats.size + }) + }) + }) + } + router.on(wire.FileSystemGetMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + plugin.retrieve(message.file) + .then(function(file) { + push.send([ + channel + , reply.okay('success', file) + ]) + }) + .catch(function(err) { + log.warn('Unable to retrieve "%s"', message.file, err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) }) - }) - }) - } - router.on(wire.FileSystemGetMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - plugin.retrieve(message.file) - .then(function(file) { - push.send([ - channel - , reply.okay('success', file) - ]) - }) - .catch(function(err) { - log.warn('Unable to retrieve "%s"', message.file, err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) - }) - }) - router.on(wire.FileSystemListMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - adb.getDevice(options.serial).readdir(message.dir) - .then(function(files) { - push.send([ - channel - , reply.okay('success', files) - ]) }) - .catch(function(err) { - log.warn('Unable to list directory "%s"', message.dir, err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) + router.on(wire.FileSystemListMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + adb.getDevice(options.serial).readdir(message.dir) + .then(function(files) { + push.send([ + channel + , reply.okay('success', files) + ]) + }) + .catch(function(err) { + log.warn('Unable to list directory "%s"', message.dir, err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) + return plugin }) - return plugin -}) diff --git a/lib/units/device/plugins/forward/index.js b/lib/units/device/plugins/forward/index.js index ab8aa12014..390bfdddca 100644 --- a/lib/units/device/plugins/forward/index.js +++ b/lib/units/device/plugins/forward/index.js @@ -20,145 +20,145 @@ export default syrup.serial() .dependency(minirev) .dependency(group) .define(function(options, adb, router, push, minirev, group) { - var log = logger.createLogger('device:plugins:forward') - var plugin = Object.create(null) - var manager = new ForwardManager() - function startService() { - log.info('Launching reverse port forwarding service') - return adb.getDevice(options.serial).shell([ - 'exec' - , minirev.bin - ]) - .then(function(out) { - lifecycle.share('Forward shell', out) - streamutil.talk(log, 'Forward shell says: "%s"', out) - }) - } - function connectService(times) { - function tryConnect(times, delay) { - return adb.getDevice(options.serial).openLocal('localabstract:minirev') - .catch(function(err) { - if (/closed/.test(err.message) && times > 1) { - return Promise.delay(delay) - .then(function() { - return tryConnect(times - 1, delay * 2) + var log = logger.createLogger('device:plugins:forward') + var plugin = Object.create(null) + var manager = new ForwardManager() + function startService() { + log.info('Launching reverse port forwarding service') + return adb.getDevice(options.serial).shell([ + 'exec' + , minirev.bin + ]) + .then(function(out) { + lifecycle.share('Forward shell', out) + streamutil.talk(log, 'Forward shell says: "%s"', out) + }) + } + function connectService(times) { + function tryConnect(times, delay) { + return adb.getDevice(options.serial).openLocal('localabstract:minirev') + .catch(function(err) { + if (/closed/.test(err.message) && times > 1) { + return Promise.delay(delay) + .then(function() { + return tryConnect(times - 1, delay * 2) + }) + } + return Promise.reject(err) }) - } - return Promise.reject(err) - }) + } + log.info('Connecting to reverse port forwarding service') + return tryConnect(times, 100) } - log.info('Connecting to reverse port forwarding service') - return tryConnect(times, 100) - } - function awaitServer() { - return connectService(5) - .then(function(conn) { - conn.end() - return true - }) - } - plugin.createForward = function(id, forward) { - log.info('Creating reverse port forward "%s" from ":%d" to "%s:%d"', id, forward.devicePort, forward.targetHost, forward.targetPort) - return connectService(1) - .then(function(out) { - var header = Buffer.alloc(4) - header.writeUInt16LE(0, 0) - header.writeUInt16LE(forward.devicePort, 2) - out.write(header) - return manager.add(id, out, forward) - }) - } - plugin.removeForward = function(id) { - log.info('Removing reverse port forward "%s"', id) - manager.remove(id) - return Promise.resolve() - } - plugin.connect = function(options) { - var resolver = Promise.defer() - var conn = net.connect({ - host: options.targetHost - , port: options.targetPort - }) - function connectListener() { - resolver.resolve(conn) + function awaitServer() { + return connectService(5) + .then(function(conn) { + conn.end() + return true + }) } - function errorListener(err) { - resolver.reject(err) + plugin.createForward = function(id, forward) { + log.info('Creating reverse port forward "%s" from ":%d" to "%s:%d"', id, forward.devicePort, forward.targetHost, forward.targetPort) + return connectService(1) + .then(function(out) { + var header = Buffer.alloc(4) + header.writeUInt16LE(0, 0) + header.writeUInt16LE(forward.devicePort, 2) + out.write(header) + return manager.add(id, out, forward) + }) } - conn.on('connect', connectListener) - conn.on('error', errorListener) - return resolver.promise.finally(function() { - conn.removeListener('connect', connectListener) - conn.removeListener('error', errorListener) - }) - } - plugin.reset = function() { - manager.removeAll(manager) - } - group.on('leave', plugin.reset) - var pushForwards = _.debounce(function() { - push.send([ - wireutil.global - , wireutil.envelope(new wire.ReverseForwardsEvent(options.serial, manager.listAll())) - ]) - }, 200) - manager.on('add', pushForwards) - manager.on('remove', pushForwards) - return startService() - .then(awaitServer) - .then(function() { - router - .on(wire.ForwardTestMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - plugin.connect(message) - .then(function(conn) { - conn.end() - push.send([ - channel - , reply.okay('success') - ]) - }) - .catch(function() { - push.send([ - channel - , reply.fail('fail_connect') - ]) - }) - }) - .on(wire.ForwardCreateMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - plugin.createForward(message.id, message) - .then(function() { - push.send([ - channel - , reply.okay('success') - ]) - }) - .catch(function(err) { - log.error('Reverse port forwarding failed', err.stack) - push.send([ - channel - , reply.fail('fail_forward') - ]) + plugin.removeForward = function(id) { + log.info('Removing reverse port forward "%s"', id) + manager.remove(id) + return Promise.resolve() + } + plugin.connect = function(options) { + var resolver = Promise.defer() + var conn = net.connect({ + host: options.targetHost + , port: options.targetPort }) - }) - .on(wire.ForwardRemoveMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - plugin.removeForward(message.id) - .then(function() { - push.send([ - channel - , reply.okay('success') - ]) + function connectListener() { + resolver.resolve(conn) + } + function errorListener(err) { + resolver.reject(err) + } + conn.on('connect', connectListener) + conn.on('error', errorListener) + return resolver.promise.finally(function() { + conn.removeListener('connect', connectListener) + conn.removeListener('error', errorListener) }) - .catch(function(err) { - log.error('Reverse port unforwarding failed', err.stack) - push.send([ - channel - , reply.fail('fail') - ]) + } + plugin.reset = function() { + manager.removeAll(manager) + } + group.on('leave', plugin.reset) + var pushForwards = _.debounce(function() { + push.send([ + wireutil.global + , wireutil.envelope(new wire.ReverseForwardsEvent(options.serial, manager.listAll())) + ]) + }, 200) + manager.on('add', pushForwards) + manager.on('remove', pushForwards) + return startService() + .then(awaitServer) + .then(function() { + router + .on(wire.ForwardTestMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + plugin.connect(message) + .then(function(conn) { + conn.end() + push.send([ + channel + , reply.okay('success') + ]) + }) + .catch(function() { + push.send([ + channel + , reply.fail('fail_connect') + ]) + }) + }) + .on(wire.ForwardCreateMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + plugin.createForward(message.id, message) + .then(function() { + push.send([ + channel + , reply.okay('success') + ]) + }) + .catch(function(err) { + log.error('Reverse port forwarding failed', err.stack) + push.send([ + channel + , reply.fail('fail_forward') + ]) + }) + }) + .on(wire.ForwardRemoveMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + plugin.removeForward(message.id) + .then(function() { + push.send([ + channel + , reply.okay('success') + ]) + }) + .catch(function(err) { + log.error('Reverse port unforwarding failed', err.stack) + push.send([ + channel + , reply.fail('fail') + ]) + }) + }) }) - }) + .return(plugin) }) - .return(plugin) -}) diff --git a/lib/units/device/plugins/group.js b/lib/units/device/plugins/group.js index 41effc2ed7..77ac306d3d 100644 --- a/lib/units/device/plugins/group.js +++ b/lib/units/device/plugins/group.js @@ -6,8 +6,8 @@ import wire from '../../../wire/index.js' import wireutil from '../../../wire/util.js' import * as grouputil from '../../../util/grouputil.js' import lifecycle from '../../../util/lifecycle.js' -import dbapi from '../../../db/api.mjs' -import apiutil from '../../../util/apiutil.js' +import * as dbapi from '../../../db/api.js' +import * as apiutil from '../../../util/apiutil.js' import solo from './solo.js' import identity from './util/identity.js' import service from './service.js' @@ -24,182 +24,183 @@ export default syrup.serial() .dependency(sub) .dependency(channels) .define(function(options, solo, ident, service, router, push, sub, channels) { - var log = logger.createLogger('device:plugins:group') - var currentGroup = null - var plugin = new events.EventEmitter() - plugin.get = Promise.method(function() { - if (!currentGroup) { - throw new grouputil.NoGroupError() - } - return currentGroup - }) - plugin.join = function(newGroup, timeout, usage) { - return plugin.get() - .then(function() { - if (currentGroup.group !== newGroup.group) { - throw new grouputil.AlreadyGroupedError() + var log = logger.createLogger('device:plugins:group') + var currentGroup = null + var plugin = new events.EventEmitter() + plugin.get = Promise.method(function() { + if (!currentGroup) { + throw new grouputil.NoGroupError() } - log.info('Update timeout for ', apiutil.QUARTER_MINUTES) - channels.updateTimeout(currentGroup.group, apiutil.QUARTER_MINUTES) - let newTimeout = channels.getTimeout(currentGroup.group) - dbapi.enhanceStatusChangedAt(options.serial, newTimeout).then(() => { - return currentGroup - }) - }) - .catch(grouputil.NoGroupError, function() { - currentGroup = newGroup - log.important('Now owned by "%s"', currentGroup.email) - log.important('Device now in group "%s"', currentGroup.name) - log.info('Rent time is ' + timeout) - log.info('Subscribing to group channel "%s"', currentGroup.group) - channels.register(currentGroup.group, { - timeout: timeout || options.groupTimeout - , alias: solo.channel - }) - dbapi.enhanceStatusChangedAt(options.serial, timeout).then(() => { - sub.subscribe(currentGroup.group) - plugin.emit('join', currentGroup) - push.send([ - wireutil.global - , wireutil.envelope(new wire.JoinGroupMessage(options.serial, currentGroup, usage)) - ]) - service.freezeRotation(0) - return currentGroup - }) + return currentGroup }) - } - plugin.keepalive = function() { - if (currentGroup) { - channels.keepalive(currentGroup.group) + plugin.join = function(newGroup, timeout, usage) { + return plugin.get() + .then(function() { + if (currentGroup.group !== newGroup.group) { + log.error(`Cannot join group ${JSON.stringify(newGroup)} since this device is in group ${JSON.stringify(currentGroup)}`) + throw new grouputil.AlreadyGroupedError() + } + log.info('Update timeout for ', apiutil.QUARTER_MINUTES) + channels.updateTimeout(currentGroup.group, apiutil.QUARTER_MINUTES) + let newTimeout = channels.getTimeout(currentGroup.group) + dbapi.enhanceStatusChangedAt(options.serial, newTimeout).then(() => { + return currentGroup + }) + }) + .catch(grouputil.NoGroupError, function() { + currentGroup = newGroup + log.important('Now owned by "%s"', currentGroup.email) + log.important('Device now in group "%s"', currentGroup.name) + log.info('Rent time is ' + timeout) + log.info('Subscribing to group channel "%s"', currentGroup.group) + channels.register(currentGroup.group, { + timeout: timeout || options.groupTimeout + , alias: solo.channel + }) + dbapi.enhanceStatusChangedAt(options.serial, timeout).then(() => { + sub.subscribe(currentGroup.group) + plugin.emit('join', currentGroup) + push.send([ + wireutil.global + , wireutil.envelope(new wire.JoinGroupMessage(options.serial, currentGroup, usage)) + ]) + service.freezeRotation(0) + return currentGroup + }) + }) } - } - plugin.leave = function(reason) { - return plugin.get() - .then(function(group) { - log.important('No longer owned by "%s"', group.email) - log.info('Unsubscribing from group channel "%s"', group.group) - dbapi.enhanceStatusChangedAt(options.serial, 0).then(() => { - push.send([ - wireutil.global - , wireutil.envelope(new wire.LeaveGroupMessage(options.serial, group, reason)) - ]) - channels.unregister(group.group) - sub.unsubscribe(group.group) - currentGroup = null - plugin.emit('leave', group) - return group - }) - }) - } - plugin.on('join', function() { - service.wake() - service.acquireWakeLock() - }) - plugin.on('leave', function() { - if (options.screenReset) { - service.pressKey('home') - service.thawRotation() - dbapi.loadDeviceBySerial(options.serial).then(device => { - if (device.group.id === device.origin) { - log.warn('Cleaning device') - service.sendCommand('settings put system screen_brightness_mode 0') - service.sendCommand('settings put system screen_brightness 0') - service.setMasterMute(true) - service.sendCommand('input keyevent 26') - service.sendCommand('settings put global http_proxy :0') - service.sendCommand('pm clear com.android.chrome') - service.sendCommand('pm clear com.chrome.beta') - service.sendCommand('pm clear com.sec.android.app.sbrowser') - service.sendCommand('pm uninstall com.vkontakte.android') - service.sendCommand('pm uninstall com.vk.im') - service.sendCommand('pm uninstall com.vk.clips') - service.sendCommand('pm uninstall com.vk.calls') - service.sendCommand('pm uninstall com.vk.admin') - service.sendCommand('pm clear com.mi.globalbrowser') - service.sendCommand('pm clear com.microsoft.emmx') - service.sendCommand('pm clear com.huawei.browser') - service.sendCommand('pm uninstall --user 0 com.samsung.clipboardsaveservice') - service.sendCommand('pm uninstall --user 0 com.samsung.android.clipboarduiservice') - service.sendCommand('rm -rf /sdcard/Downloads') - service.sendCommand('rm -rf /storage/emulated/legacy/Downloads') - service.sendCommand('settings put global always_finish_activities 0') - service.sendCommand('pm enable-user com.google.android.gms') - service.sendCommand('settings put system font_scale 1.0') - service.sendCommand('su') - service.sendCommand('echo "chrome --disable-fre --no-default-browser-check —no-first-run" > /data/local/tmp/chrome-command-line') - service.sendCommand('am set-debug-app --persistent com.android.chrome') - } - else { - log.warn('Device was not cleared because it in custom group') - } - }) + plugin.keepalive = function() { + if (currentGroup) { + channels.keepalive(currentGroup.group) + } } - service.releaseWakeLock() - }) - router - .on(wire.GroupMessage, function(channel, message) { - let reply = wireutil.reply(options.serial) - grouputil.match(ident, message.requirements) - .then(function() { - return plugin.join(message.owner, message.timeout, message.usage) - }) - .then(function() { - push.send([ - channel - , reply.okay() - ]) - }) - .catch(grouputil.RequirementMismatchError, function(err) { - push.send([ - channel - , reply.fail(err.message) - ]) - }) - .catch(grouputil.AlreadyGroupedError, function(err) { - push.send([ - channel - , reply.fail(err.message) - ]) - }) - }) - .on(wire.AutoGroupMessage, function(channel, message) { - return plugin.join(message.owner, message.timeout, message.identifier) - .then(function() { - plugin.emit('autojoin', message.identifier, true) - }) - .catch(grouputil.AlreadyGroupedError, function() { - plugin.emit('autojoin', message.identifier, false) - }) - }) - .on(wire.UngroupMessage, function(channel, message) { - let reply = wireutil.reply(options.serial) - grouputil.match(ident, message.requirements) - .then(function() { - return plugin.leave('ungroup_request') + plugin.leave = function(reason) { + return plugin.get() + .then(function(group) { + log.important('No longer owned by "%s"', group.email) + log.info('Unsubscribing from group channel "%s"', group.group) + dbapi.enhanceStatusChangedAt(options.serial, 0).then(() => { + push.send([ + wireutil.global + , wireutil.envelope(new wire.LeaveGroupMessage(options.serial, group, reason)) + ]) + channels.unregister(group.group) + sub.unsubscribe(group.group) + currentGroup = null + plugin.emit('leave', group) + return group + }) + }) + } + plugin.on('join', function() { + service.wake() + service.acquireWakeLock() }) - .then(function() { - push.send([ - channel - , reply.okay() - ]) + plugin.on('leave', function() { + if (options.screenReset) { + service.pressKey('home') + service.thawRotation() + dbapi.loadDeviceBySerial(options.serial).then(device => { + if (device.group.id === device.origin) { + log.warn('Cleaning device') + service.sendCommand('settings put system screen_brightness_mode 0') + service.sendCommand('settings put system screen_brightness 0') + service.setMasterMute(true) + service.sendCommand('input keyevent 26') + service.sendCommand('settings put global http_proxy :0') + service.sendCommand('pm clear com.android.chrome') + service.sendCommand('pm clear com.chrome.beta') + service.sendCommand('pm clear com.sec.android.app.sbrowser') + service.sendCommand('pm uninstall com.vkontakte.android') + service.sendCommand('pm uninstall com.vk.im') + service.sendCommand('pm uninstall com.vk.clips') + service.sendCommand('pm uninstall com.vk.calls') + service.sendCommand('pm uninstall com.vk.admin') + service.sendCommand('pm clear com.mi.globalbrowser') + service.sendCommand('pm clear com.microsoft.emmx') + service.sendCommand('pm clear com.huawei.browser') + service.sendCommand('pm uninstall --user 0 com.samsung.clipboardsaveservice') + service.sendCommand('pm uninstall --user 0 com.samsung.android.clipboarduiservice') + service.sendCommand('rm -rf /sdcard/Downloads') + service.sendCommand('rm -rf /storage/emulated/legacy/Downloads') + service.sendCommand('settings put global always_finish_activities 0') + service.sendCommand('pm enable-user com.google.android.gms') + service.sendCommand('settings put system font_scale 1.0') + service.sendCommand('su') + service.sendCommand('echo "chrome --disable-fre --no-default-browser-check —no-first-run" > /data/local/tmp/chrome-command-line') + service.sendCommand('am set-debug-app --persistent com.android.chrome') + } + else { + log.warn('Device was not cleared because it in custom group') + } + }) + } + service.releaseWakeLock() }) - .catch(grouputil.NoGroupError, function(err) { - push.send([ - channel - , reply.fail(err.message) - ]) + router + .on(wire.GroupMessage, function(channel, message) { + let reply = wireutil.reply(options.serial) + grouputil.match(ident, message.requirements) + .then(function() { + return plugin.join(message.owner, message.timeout, message.usage) + }) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(grouputil.RequirementMismatchError, function(err) { + push.send([ + channel + , reply.fail(err.message) + ]) + }) + .catch(grouputil.AlreadyGroupedError, function(err) { + push.send([ + channel + , reply.fail(err.message) + ]) + }) + }) + .on(wire.AutoGroupMessage, function(channel, message) { + return plugin.join(message.owner, message.timeout, message.identifier) + .then(function() { + plugin.emit('autojoin', message.identifier, true) + }) + .catch(grouputil.AlreadyGroupedError, function() { + plugin.emit('autojoin', message.identifier, false) + }) + }) + .on(wire.UngroupMessage, function(channel, message) { + let reply = wireutil.reply(options.serial) + grouputil.match(ident, message.requirements) + .then(function() { + return plugin.leave('ungroup_request') + }) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(grouputil.NoGroupError, function(err) { + push.send([ + channel + , reply.fail(err.message) + ]) + }) + }) + channels.on('timeout', function(channel) { + if (currentGroup && channel === currentGroup.group) { + plugin.leave('automatic_timeout') + } }) - }) - channels.on('timeout', function(channel) { - if (currentGroup && channel === currentGroup.group) { - plugin.leave('automatic_timeout') - } - }) - lifecycle.observe(function() { - return plugin.leave('device_absent') - .catch(grouputil.NoGroupError, function() { - return true + lifecycle.observe(function() { + return plugin.leave('device_absent') + .catch(grouputil.NoGroupError, function() { + return true + }) }) + return plugin }) - return plugin -}) diff --git a/lib/units/device/plugins/heartbeat.js b/lib/units/device/plugins/heartbeat.js index e55d467abe..b4083bbee2 100644 --- a/lib/units/device/plugins/heartbeat.js +++ b/lib/units/device/plugins/heartbeat.js @@ -6,12 +6,12 @@ import push from '../../base-device/support/push.js' export default syrup.serial() .dependency(push) .define((options, push) => { - function beat() { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceHeartbeatMessage(options.serial)) - ]) - } - let timer = setInterval(beat, options.heartbeatInterval) - lifecycle.observe(() => clearInterval(timer)) -}) + function beat() { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceHeartbeatMessage(options.serial)) + ]) + } + let timer = setInterval(beat, options.heartbeatInterval) + lifecycle.observe(() => clearInterval(timer)) + }) diff --git a/lib/units/device/plugins/install.js b/lib/units/device/plugins/install.js index d220a0dcb5..c66757d744 100644 --- a/lib/units/device/plugins/install.js +++ b/lib/units/device/plugins/install.js @@ -9,7 +9,7 @@ import wire from '../../../wire/index.js' import wireutil from '../../../wire/util.js' import * as promiseutil from '../../../util/promiseutil.js' import adbkit from '@devicefarmer/adbkit' -import apiutil from '../../../util/apiutil.js' +import * as apiutil from '../../../util/apiutil.js' import adb from '../support/adb.js' import router from '../../base-device/support/router.js' import push from '../../base-device/support/push.js' @@ -24,194 +24,193 @@ export default syrup.serial() .dependency(router) .dependency(push) .define(function(options, adb, router, push) { - let log = logger.createLogger('device:plugins:install') - router.on(wire.InstallMessage, function(channel, message) { - let manifest = JSON.parse(message.manifest) - let pkg = manifest.package - let installFlags = message.installFlags - let isApi = message.isApi - log.info('Installing package "%s" from "%s"', pkg, message.href) - let reply = wireutil.reply(options.serial) - function sendProgress(data, progress) { - if (isApi) { - return - } - push.send([ - channel - , reply.progress(data, progress) - ]) - } - function pushApp(channel) { - let href = message.href - let contentLength = null - let req = request(url.resolve(options.storageUrl, href), { - headers: { - channel: channel - , device: options.serial - } - , timeout: apiutil.INSTALL_APK_WAIT - }) - // We need to catch the Content-Length on the fly or we risk - // losing some of the initial chunks. - req.on('response', function(res) { - contentLength = parseInt(res.headers['content-length'], 10) - }) - req.on('error', function(err) { - log.error(err) - }) - let source = new stream.Readable().wrap(req) - let target = '/data/local/tmp/_app.apk' - return adb.getDevice(options.serial).push(source, target) - .then(function(transfer) { - let resolver = Promise.defer() - function progressListener(stats) { - if (contentLength) { - // Progress 0% to 70% - sendProgress('pushing_app', 50 * Math.max(0, Math.min(50, stats.bytesTransferred / contentLength))) - // temporary workaround as the 'end' event is never fired - if (stats.bytesTransferred === contentLength) { - setTimeout(() => { - endListener() - }, apiutil.ONE_SECOND) - } - } - } - function errorListener(err) { - resolver.reject(err) - } - function endListener() { - resolver.resolve(target) + let log = logger.createLogger('device:plugins:install') + router.on(wire.InstallMessage, function(channel, message) { + let manifest = JSON.parse(message.manifest) + let pkg = manifest.package + let installFlags = message.installFlags + let isApi = message.isApi + log.info('Installing package "%s" from "%s"', pkg, message.href) + let reply = wireutil.reply(options.serial) + function sendProgress(data, progress) { + if (isApi) { + return } - transfer.on('progress', progressListener) - transfer.on('error', errorListener) - transfer.on('end', endListener) - return resolver.promise.finally(function() { - transfer.removeListener('progress', progressListener) - transfer.removeListener('error', errorListener) - transfer.removeListener('end', endListener) - }) - }) - } - // Progress 0% - sendProgress('pushing_app', 0) - pushApp(channel) - .then(function(apk) { - let start = 50 - let end = 90 - let guesstimate = start - let installCmd = 'pm install ' - if (installFlags.length > 0) { - installCmd += installFlags.join(' ') + ' ' + push.send([ + channel + , reply.progress(data, progress) + ]) } - installCmd += apk - log.info('Install command: ' + installCmd) - sendProgress('installing_app', guesstimate) - return promiseutil.periodicNotify(adb.getDevice(options.serial).shell(installCmd) - .then((r) => { - adbkit.Adb.util.readAll(r) - .then(buffer => { - let result = buffer.toString() - log.info('Installing result ' + result) - if (result.includes('Success')) { - push.send([ - channel - , reply.okay('Installed successfully') - ]) - push.send([ - channel - , wireutil.envelope(new wire.InstallResultMessage(options.serial, 'Installed successfully')) - ]) - } - else { - // eslint-disable-next-line max-len - if (result.includes('INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES') || result.includes('INSTALL_FAILED_VERSION_DOWNGRADE')) { - log.info('Uninstalling "%s" first due to inconsistent certificates', pkg) - return adb.getDevice(options.serial).uninstall(pkg) - .then(function() { - return adb.getDevice(options.serial).shell(installCmd) - }) - } - else { - log.error('Tried to install package "%s", got "%s"', pkg, result) - push.send([ - channel - , reply.fail(result) - ]) - push.send([ - channel - , wireutil.envelope(new wire.InstallResultMessage(options.serial, 'Tried to install package ' + pkg + ', got ' + result)) - ]) - return Promise.reject(result) - } + function pushApp(channel) { + let href = message.href + let contentLength = null + let req = request(url.resolve(options.storageUrl, href), { + headers: { + channel: channel + , device: options.serial } + , timeout: apiutil.INSTALL_APK_WAIT }) - .then(function() { - if (message.launch) { - if (manifest.application.launcherActivities.length) { - let activityName = 'MainActivity' - // According to the AndroidManifest.xml documentation the dot is - // required, but actually it isn't. - if (activityName.indexOf('.') === -1) { - activityName = util.format('.%s', activityName) - } - let launchActivity = { - action: 'android.intent.action.MAIN' - , component: util.format('%s/%s', pkg, activityName) - , category: ['android.intent.category.LAUNCHER'] - , flags: 0x10200000 + // We need to catch the Content-Length on the fly or we risk + // losing some of the initial chunks. + req.on('response', function(res) { + contentLength = parseInt(res.headers['content-length'], 10) + }) + req.on('error', function(err) { + log.error(err) + }) + let source = new stream.Readable().wrap(req) + let target = '/data/local/tmp/_app.apk' + return adb.getDevice(options.serial).push(source, target) + .then(function(transfer) { + let resolver = Promise.defer() + function progressListener(stats) { + if (contentLength) { + // Progress 0% to 70% + sendProgress('pushing_app', 50 * Math.max(0, Math.min(50, stats.bytesTransferred / contentLength))) + // temporary workaround as the 'end' event is never fired + if (stats.bytesTransferred === contentLength) { + setTimeout(() => { + endListener() + }, apiutil.ONE_SECOND) + } } - log.info('Launching activity with action "%s" on component "%s"', launchActivity.action, launchActivity.component) - // Progress 90% - sendProgress('launching_app', 90) - return adb.getDevice(options.serial).startActivity(launchActivity) } + function errorListener(err) { + resolver.reject(err) + } + function endListener() { + resolver.resolve(target) + } + transfer.on('progress', progressListener) + transfer.on('error', errorListener) + transfer.on('end', endListener) + return resolver.promise.finally(function() { + transfer.removeListener('progress', progressListener) + transfer.removeListener('error', errorListener) + transfer.removeListener('end', endListener) + }) + }) + } + // Progress 0% + sendProgress('pushing_app', 0) + pushApp(channel) + .then(function(apk) { + let start = 50 + let end = 90 + let guesstimate = start + let installCmd = 'pm install ' + if (installFlags.length > 0) { + installCmd += installFlags.join(' ') + ' ' } + installCmd += apk + log.info('Install command: ' + installCmd) + sendProgress('installing_app', guesstimate) + return promiseutil.periodicNotify(adb.getDevice(options.serial).shell(installCmd) + .then((r) => { + adbkit.Adb.util.readAll(r) + .then(buffer => { + let result = buffer.toString() + log.info('Installing result ' + result) + if (result.includes('Success')) { + push.send([ + channel + , reply.okay('Installed successfully') + ]) + push.send([ + channel + , wireutil.envelope(new wire.InstallResultMessage(options.serial, 'Installed successfully')) + ]) + } + else { + if (result.includes('INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES') || result.includes('INSTALL_FAILED_VERSION_DOWNGRADE')) { + log.info('Uninstalling "%s" first due to inconsistent certificates', pkg) + return adb.getDevice(options.serial).uninstall(pkg) + .then(function() { + return adb.getDevice(options.serial).shell(installCmd) + }) + } + else { + log.error('Tried to install package "%s", got "%s"', pkg, result) + push.send([ + channel + , reply.fail(result) + ]) + push.send([ + channel + , wireutil.envelope(new wire.InstallResultMessage(options.serial, 'Tried to install package ' + pkg + ', got ' + result)) + ]) + return Promise.reject(result) + } + } + }) + .then(function() { + if (message.launch) { + if (manifest.application.launcherActivities.length) { + let activityName = 'MainActivity' + // According to the AndroidManifest.xml documentation the dot is + // required, but actually it isn't. + if (activityName.indexOf('.') === -1) { + activityName = util.format('.%s', activityName) + } + let launchActivity = { + action: 'android.intent.action.MAIN' + , component: util.format('%s/%s', pkg, activityName) + , category: ['android.intent.category.LAUNCHER'] + , flags: 0x10200000 + } + log.info('Launching activity with action "%s" on component "%s"', launchActivity.action, launchActivity.component) + // Progress 90% + sendProgress('launching_app', 90) + return adb.getDevice(options.serial).startActivity(launchActivity) + } + } + }) + }) + .catch(function(err) { + log.error('Error while installation \n') + log.error(err) + return Promise.reject(err) + }), 250) + .progressed(function() { + if (!isApi) { + return + } + guesstimate = Math.min(end, guesstimate + 1.5 * (end - guesstimate) / (end - start)) + sendProgress('installing_app', guesstimate) + }) + }) + .catch(Promise.TimeoutError, function(err) { + log.error('Installation of package "%s" failed', pkg, err.stack) + push.send([ + channel + , reply.fail('INSTALL_ERROR_TIMEOUT') + ]) }) - }) .catch(function(err) { - log.error('Error while installation \n') - log.error(err) - return Promise.reject(err) - }), 250) - .progressed(function() { - if (!isApi) { - return - } - guesstimate = Math.min(end, guesstimate + 1.5 * (end - guesstimate) / (end - start)) - sendProgress('installing_app', guesstimate) - }) - }) - .catch(Promise.TimeoutError, function(err) { - log.error('Installation of package "%s" failed', pkg, err.stack) - push.send([ - channel - , reply.fail('INSTALL_ERROR_TIMEOUT') - ]) - }) - .catch(function(err) { - log.error('Installation of package "%s" failed', pkg, err.stack) - push.send([ - channel - , reply.fail('INSTALL_ERROR_UNKNOWN') - ]) - }) - }) - router.on(wire.UninstallMessage, function(channel, message) { - log.info('Uninstalling "%s"', message.packageName) - let reply = wireutil.reply(options.serial) - adb.getDevice(options.serial).uninstall(message.packageName) - .then(function() { - push.send([ - channel - , reply.okay('success') - ]) + log.error('Installation of package "%s" failed', pkg, err.stack) + push.send([ + channel + , reply.fail('INSTALL_ERROR_UNKNOWN') + ]) + }) }) - .catch(function(err) { - log.error('Uninstallation failed', err.stack) - push.send([ - channel - , reply.fail('fail') - ]) + router.on(wire.UninstallMessage, function(channel, message) { + log.info('Uninstalling "%s"', message.packageName) + let reply = wireutil.reply(options.serial) + adb.getDevice(options.serial).uninstall(message.packageName) + .then(function() { + push.send([ + channel + , reply.okay('success') + ]) + }) + .catch(function(err) { + log.error('Uninstallation failed', err.stack) + push.send([ + channel + , reply.fail('fail') + ]) + }) }) }) -}) diff --git a/lib/units/device/plugins/logcat.js b/lib/units/device/plugins/logcat.js index e22ef1c0b3..a612cc2c5a 100644 --- a/lib/units/device/plugins/logcat.js +++ b/lib/units/device/plugins/logcat.js @@ -14,111 +14,111 @@ export default syrup.serial() .dependency(push) .dependency(group) .define(function(options, adb, router, push, group) { - var log = logger.createLogger('device:plugins:logcat') - var plugin = Object.create(null) - var activeLogcat = null - plugin.start = function(filters) { - return group.get() - .then(function(group) { - return plugin.stop() - .then(function() { - log.info('Starting logcat') - return adb.getDevice(options.serial).openLogcat({ - clear: true + var log = logger.createLogger('device:plugins:logcat') + var plugin = Object.create(null) + var activeLogcat = null + plugin.start = function(filters) { + return group.get() + .then(function(group) { + return plugin.stop() + .then(function() { + log.info('Starting logcat') + return adb.getDevice(options.serial).openLogcat({ + clear: true + }) + }) + .timeout(10000) + .then(function(logcat) { + activeLogcat = logcat + function entryListener(entry) { + push.send([ + group.group + , wireutil.envelope(new wire.DeviceLogcatEntryMessage(options.serial, entry.date.getTime() / 1000, entry.pid, entry.tid, entry.priority, entry.tag, entry.message)) + ]) + } + logcat.on('entry', entryListener) + return plugin.reset(filters) + }) }) - }) - .timeout(10000) - .then(function(logcat) { - activeLogcat = logcat - function entryListener(entry) { - push.send([ - group.group - , wireutil.envelope(new wire.DeviceLogcatEntryMessage(options.serial, entry.date.getTime() / 1000, entry.pid, entry.tid, entry.priority, entry.tag, entry.message)) - ]) - } - logcat.on('entry', entryListener) - return plugin.reset(filters) - }) - }) - } - plugin.stop = Promise.method(function() { - if (plugin.isRunning()) { - log.info('Stopping logcat') - activeLogcat.end() - activeLogcat = null } - }) - plugin.reset = Promise.method(function(filters) { - if (plugin.isRunning()) { - activeLogcat - .resetFilters() - if (filters.length) { - activeLogcat.excludeAll() - filters.forEach(function(filter) { - activeLogcat.include(filter.tag, filter.priority) - }) + plugin.stop = Promise.method(function() { + if (plugin.isRunning()) { + log.info('Stopping logcat') + activeLogcat.end() + activeLogcat = null } - } - else { - throw new Error('Logcat is not running') - } - }) - plugin.isRunning = function() { - return !!activeLogcat - } - lifecycle.observe(plugin.stop) - group.on('leave', plugin.stop) - router - .on(wire.LogcatStartMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - plugin.start(message.filters) - .then(function() { - push.send([ - channel - , reply.okay('success') - ]) - }) - .catch(function(err) { - log.error('Unable to open logcat', err.stack) - push.send([ - channel - , reply.fail('fail') - ]) - }) - }) - .on(wire.LogcatApplyFiltersMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - plugin.reset(message.filters) - .then(function() { - push.send([ - channel - , reply.okay('success') - ]) - }) - .catch(function(err) { - log.error('Failed to apply logcat filters', err.stack) - push.send([ - channel - , reply.fail('fail') - ]) - }) - }) - .on(wire.LogcatStopMessage, function(channel) { - var reply = wireutil.reply(options.serial) - plugin.stop() - .then(function() { - push.send([ - channel - , reply.okay('success') - ]) }) - .catch(function(err) { - log.error('Failed to stop logcat', err.stack) - push.send([ - channel - , reply.fail('fail') - ]) + plugin.reset = Promise.method(function(filters) { + if (plugin.isRunning()) { + activeLogcat + .resetFilters() + if (filters.length) { + activeLogcat.excludeAll() + filters.forEach(function(filter) { + activeLogcat.include(filter.tag, filter.priority) + }) + } + } + else { + throw new Error('Logcat is not running') + } }) + plugin.isRunning = function() { + return !!activeLogcat + } + lifecycle.observe(plugin.stop) + group.on('leave', plugin.stop) + router + .on(wire.LogcatStartMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + plugin.start(message.filters) + .then(function() { + push.send([ + channel + , reply.okay('success') + ]) + }) + .catch(function(err) { + log.error('Unable to open logcat', err.stack) + push.send([ + channel + , reply.fail('fail') + ]) + }) + }) + .on(wire.LogcatApplyFiltersMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + plugin.reset(message.filters) + .then(function() { + push.send([ + channel + , reply.okay('success') + ]) + }) + .catch(function(err) { + log.error('Failed to apply logcat filters', err.stack) + push.send([ + channel + , reply.fail('fail') + ]) + }) + }) + .on(wire.LogcatStopMessage, function(channel) { + var reply = wireutil.reply(options.serial) + plugin.stop() + .then(function() { + push.send([ + channel + , reply.okay('success') + ]) + }) + .catch(function(err) { + log.error('Failed to stop logcat', err.stack) + push.send([ + channel + , reply.fail('fail') + ]) + }) + }) + return plugin }) - return plugin -}) diff --git a/lib/units/device/plugins/logger.js b/lib/units/device/plugins/logger.js index f4be287a5a..1249577119 100644 --- a/lib/units/device/plugins/logger.js +++ b/lib/units/device/plugins/logger.js @@ -7,11 +7,11 @@ export default syrup.serial() .dependency(push) .define(function(options, push) { // Forward all logs - logger.on('entry', function(entry) { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceLogMessage(options.serial, entry.timestamp / 1000, entry.priority, entry.tag, entry.pid, entry.message, entry.identifier)) - ]) + logger.on('entry', function(entry) { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceLogMessage(options.serial, entry.timestamp / 1000, entry.priority, entry.tag, entry.pid, entry.message, entry.identifier)) + ]) + }) + return logger }) - return logger -}) diff --git a/lib/units/device/plugins/mobile-service.js b/lib/units/device/plugins/mobile-service.js index 4f56792a3f..842254dc12 100644 --- a/lib/units/device/plugins/mobile-service.js +++ b/lib/units/device/plugins/mobile-service.js @@ -8,18 +8,18 @@ export default syrup.serial() .dependency(push) .dependency(service) .define(function(options, push, service) { - const log = logger.createLogger('device:plugins:mobile-service') - function updateMobileServices(data) { - log.info('Updating mobile services list') - push.send([ - wireutil.global - , wireutil.envelope(new wire.GetServicesAvailabilityMessage(options.serial, data.hasGMS, data.hasHMS)) - ]) - } - function loadMobileServices() { - log.info('Loading mobile services list') - return service.getMobileServices() - .then(updateMobileServices) - } - return loadMobileServices() -}) + const log = logger.createLogger('device:plugins:mobile-service') + function updateMobileServices(data) { + log.info('Updating mobile services list') + push.send([ + wireutil.global + , wireutil.envelope(new wire.GetServicesAvailabilityMessage(options.serial, data.hasGMS, data.hasHMS)) + ]) + } + function loadMobileServices() { + log.info('Loading mobile services list') + return service.getMobileServices() + .then(updateMobileServices) + } + return loadMobileServices() + }) diff --git a/lib/units/device/plugins/mute.js b/lib/units/device/plugins/mute.js index 516e594267..24baf2c636 100644 --- a/lib/units/device/plugins/mute.js +++ b/lib/units/device/plugins/mute.js @@ -7,8 +7,8 @@ export default syrup.serial() .dependency(group) .dependency(service) .define(function(options, group, service) { - var log = logger.createLogger('device:plugins:mute') - switch (options.muteMaster) { + var log = logger.createLogger('device:plugins:mute') + switch (options.muteMaster) { case 'always': log.info('Pre-emptively muting master volume') service.setMasterMute(true) @@ -32,6 +32,6 @@ export default syrup.serial() default: log.info('Will not mute master volume') break - } - return Promise.resolve() -}) + } + return Promise.resolve() + }) diff --git a/lib/units/device/plugins/reboot.js b/lib/units/device/plugins/reboot.js index 73ba5288f6..5a1879e1b0 100644 --- a/lib/units/device/plugins/reboot.js +++ b/lib/units/device/plugins/reboot.js @@ -10,23 +10,23 @@ export default syrup.serial() .dependency(router) .dependency(push) .define(function(options, adb, router, push) { - const log = logger.createLogger('device:plugins:reboot') - router.on(wire.RebootMessage, function(channel) { - let reply = wireutil.reply(options.serial) - log.important('Rebooting') - adb.getDevice(options.serial).reboot() - .then(function() { - push.send([ - channel - , reply.okay() - ]) - }) - .error(function(err) { - log.error('Reboot failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) + const log = logger.createLogger('device:plugins:reboot') + router.on(wire.RebootMessage, function(channel) { + let reply = wireutil.reply(options.serial) + log.important('Rebooting') + adb.getDevice(options.serial).reboot() + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .error(function(err) { + log.error('Reboot failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) }) -}) diff --git a/lib/units/device/plugins/remotedebug.js b/lib/units/device/plugins/remotedebug.js index 39003cbc6c..134f551ebb 100644 --- a/lib/units/device/plugins/remotedebug.js +++ b/lib/units/device/plugins/remotedebug.js @@ -8,17 +8,17 @@ export default syrup.serial() .dependency(group) .dependency(push) .define((options, group, push) => { - const log = logger.createLogger('device:plugins:remotedebug') - const updateRemoteConnectUrl = (group) => { - push.send([ - group.group - , wireutil.envelope(new wire.UpdateRemoteConnectUrl(options.serial)) - ]) - } - group.on('join', (group) => { - updateRemoteConnectUrl(group) - }) - group.on('leave', () => { + const log = logger.createLogger('device:plugins:remotedebug') + const updateRemoteConnectUrl = (group) => { + push.send([ + group.group + , wireutil.envelope(new wire.UpdateRemoteConnectUrl(options.serial)) + ]) + } + group.on('join', (group) => { + updateRemoteConnectUrl(group) + }) + group.on('leave', () => { // do nothing + }) }) -}) diff --git a/lib/units/device/plugins/ringer.js b/lib/units/device/plugins/ringer.js index 661876a628..e45c07a6c0 100644 --- a/lib/units/device/plugins/ringer.js +++ b/lib/units/device/plugins/ringer.js @@ -10,43 +10,43 @@ export default syrup.serial() .dependency(router) .dependency(push) .define(function(options, service, router, push) { - var log = logger.createLogger('device:plugins:ringer') - router.on(wire.RingerSetMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - log.info('Setting ringer mode to mode "%s"', message.mode) - service.setRingerMode(message.mode) - .timeout(30000) - .then(function() { - push.send([ - channel - , reply.okay() - ]) + var log = logger.createLogger('device:plugins:ringer') + router.on(wire.RingerSetMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + log.info('Setting ringer mode to mode "%s"', message.mode) + service.setRingerMode(message.mode) + .timeout(30000) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(function(err) { + log.error('Setting ringer mode failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) - .catch(function(err) { - log.error('Setting ringer mode failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) + router.on(wire.RingerGetMessage, function(channel) { + var reply = wireutil.reply(options.serial) + log.info('Getting ringer mode') + service.getRingerMode() + .timeout(30000) + .then(function(mode) { + push.send([ + channel + , reply.okay('success', mode) + ]) + }) + .catch(function(err) { + log.error('Getting ringer mode failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) }) - router.on(wire.RingerGetMessage, function(channel) { - var reply = wireutil.reply(options.serial) - log.info('Getting ringer mode') - service.getRingerMode() - .timeout(30000) - .then(function(mode) { - push.send([ - channel - , reply.okay('success', mode) - ]) - }) - .catch(function(err) { - log.error('Getting ringer mode failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) - }) - }) -}) diff --git a/lib/units/device/plugins/screen/capture.js b/lib/units/device/plugins/screen/capture.js index cce4c3dd97..b78c326e2b 100644 --- a/lib/units/device/plugins/screen/capture.js +++ b/lib/units/device/plugins/screen/capture.js @@ -18,52 +18,52 @@ export default syrup.serial() .dependency(minicap) .dependency(display) .define(function(options, adb, router, push, storage, minicap, display) { - var log = logger.createLogger('device:plugins:screen:capture') - var plugin = Object.create(null) - function projectionFormat() { - return util.format('%dx%d@%dx%d/%d', display.properties.width, display.properties.height, display.properties.width, display.properties.height, display.properties.rotation) - } - plugin.capture = function() { - var file = util.format('/data/local/tmp/minicap_%d.jpg', Date.now()) - return minicap.run('minicap-apk', util.format('-P %s -s >%s', projectionFormat(), file)) - .then(adbkit.Adb.util.readAll) - .then(function() { - return adb.getDevice(options.serial).stat(file) - }) - .then(function(stats) { - if (stats.size === 0) { - throw new Error('Empty screenshot; possibly secure screen?') - } - return adb.getDevice(options.serial).pull(file) - .then(function(transfer) { - return storage.store('image', transfer, { - filename: util.format('%s.jpg', options.serial) - , contentType: 'image/jpeg' - , knownLength: stats.size - }) - }) - }) - .finally(function() { - return adb.getDevice(options.serial).shell(['rm', '-f', file]) + var log = logger.createLogger('device:plugins:screen:capture') + var plugin = Object.create(null) + function projectionFormat() { + return util.format('%dx%d@%dx%d/%d', display.properties.width, display.properties.height, display.properties.width, display.properties.height, display.properties.rotation) + } + plugin.capture = function() { + var file = util.format('/data/local/tmp/minicap_%d.jpg', Date.now()) + return minicap.run('minicap-apk', util.format('-P %s -s >%s', projectionFormat(), file)) .then(adbkit.Adb.util.readAll) + .then(function() { + return adb.getDevice(options.serial).stat(file) + }) + .then(function(stats) { + if (stats.size === 0) { + throw new Error('Empty screenshot; possibly secure screen?') + } + return adb.getDevice(options.serial).pull(file) + .then(function(transfer) { + return storage.store('image', transfer, { + filename: util.format('%s.jpg', options.serial) + , contentType: 'image/jpeg' + , knownLength: stats.size + }) + }) + }) + .finally(function() { + return adb.getDevice(options.serial).shell(['rm', '-f', file]) + .then(adbkit.Adb.util.readAll) + }) + } + router.on(wire.ScreenCaptureMessage, function(channel) { + var reply = wireutil.reply(options.serial) + plugin.capture() + .then(function(file) { + push.send([ + channel + , reply.okay('success', file) + ]) + }) + .catch(function(err) { + log.error('Screen capture failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) - } - router.on(wire.ScreenCaptureMessage, function(channel) { - var reply = wireutil.reply(options.serial) - plugin.capture() - .then(function(file) { - push.send([ - channel - , reply.okay('success', file) - ]) - }) - .catch(function(err) { - log.error('Screen capture failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) - }) + return plugin }) - return plugin -}) diff --git a/lib/units/device/plugins/screen/options.js b/lib/units/device/plugins/screen/options.js index dfe74dae48..80b3998c44 100644 --- a/lib/units/device/plugins/screen/options.js +++ b/lib/units/device/plugins/screen/options.js @@ -2,13 +2,13 @@ import syrup from '@devicefarmer/stf-syrup' import _ from 'lodash' export default syrup.serial() .define(function(options) { - var plugin = Object.create(null) - plugin.devicePort = 9002 - plugin.publicPort = options.screenPort - plugin.publicUrl = _.template(options.screenWsUrlPattern)({ - publicIp: options.publicIp - , publicPort: plugin.publicPort - , serial: options.serial + var plugin = Object.create(null) + plugin.devicePort = 9002 + plugin.publicPort = options.screenPort + plugin.publicUrl = _.template(options.screenWsUrlPattern)({ + publicIp: options.publicIp + , publicPort: plugin.publicPort + , serial: options.serial + }) + return plugin }) - return plugin -}) diff --git a/lib/units/device/plugins/screen/stream.js b/lib/units/device/plugins/screen/stream.js index 18da21b32e..bf1855a7d2 100644 --- a/lib/units/device/plugins/screen/stream.js +++ b/lib/units/device/plugins/screen/stream.js @@ -30,43 +30,43 @@ export default syrup.serial() .dependency(display) .dependency(options) .define(function(options, adb, router, minicap, scrcpy, display, screenOptions) { - let log = logger.createLogger('device:plugins:screen:stream') - log.info('ScreenGrabber option set to %s', options.screenGrabber) - log.info('ScreenFrameRate option set to %d', options.screenFrameRate) - const scrcpyClient = new scrcpy.Scrcpy() - function FrameProducer(config, grabber) { - EventEmitter.call(this) - this.actionQueue = [] - this.runningState = FrameProducer.STATE_STOPPED - this.desiredState = new StateQueue() - this.output = null - this.socket = null - this.pid = -1 - this.banner = null - this.parser = null - this.frameConfig = config - this.grabber = options.screenGrabber - this.readable = false - this.needsReadable = false - this.failCounter = new FailCounter(3, 10000) - this.failCounter.on('exceedLimit', this._failLimitExceeded.bind(this)) - this.failed = false - this.readableListener = this._readableListener.bind(this) - } - util.inherits(FrameProducer, EventEmitter) - FrameProducer.STATE_STOPPED = 1 - FrameProducer.STATE_STARTING = 2 - FrameProducer.STATE_STARTED = 3 - FrameProducer.STATE_STOPPING = 4 - FrameProducer.prototype._ensureState = function() { - if (this.desiredState.empty()) { - return - } - if (this.failed) { - log.warn('Will not apply desired state due to too many failures') - return + let log = logger.createLogger('device:plugins:screen:stream') + log.info('ScreenGrabber option set to %s', options.screenGrabber) + log.info('ScreenFrameRate option set to %d', options.screenFrameRate) + const scrcpyClient = new scrcpy.Scrcpy() + function FrameProducer(config, grabber) { + EventEmitter.call(this) + this.actionQueue = [] + this.runningState = FrameProducer.STATE_STOPPED + this.desiredState = new StateQueue() + this.output = null + this.socket = null + this.pid = -1 + this.banner = null + this.parser = null + this.frameConfig = config + this.grabber = options.screenGrabber + this.readable = false + this.needsReadable = false + this.failCounter = new FailCounter(3, 10000) + this.failCounter.on('exceedLimit', this._failLimitExceeded.bind(this)) + this.failed = false + this.readableListener = this._readableListener.bind(this) } - switch (this.runningState) { + util.inherits(FrameProducer, EventEmitter) + FrameProducer.STATE_STOPPED = 1 + FrameProducer.STATE_STARTING = 2 + FrameProducer.STATE_STARTED = 3 + FrameProducer.STATE_STOPPING = 4 + FrameProducer.prototype._ensureState = function() { + if (this.desiredState.empty()) { + return + } + if (this.failed) { + log.warn('Will not apply desired state due to too many failures') + return + } + switch (this.runningState) { case FrameProducer.STATE_STARTING: case FrameProducer.STATE_STOPPING: // Just wait. @@ -77,49 +77,49 @@ export default syrup.serial() if (options.needScrcpy) { scrcpyClient.start() .then((function() { - this.runningState = FrameProducer.STATE_STARTED - this.emit('start') - }).bind(this)) + this.runningState = FrameProducer.STATE_STARTED + this.emit('start') + }).bind(this)) } else { this._startService().bind(this) .then(function(out) { - this.output = new RiskyStream(out) - .on('unexpectedEnd', this._outputEnded.bind(this)) - return this._readOutput(this.output.stream) - }) + this.output = new RiskyStream(out) + .on('unexpectedEnd', this._outputEnded.bind(this)) + return this._readOutput(this.output.stream) + }) .then(function() { - return this._waitForPid() - }) + return this._waitForPid() + }) .then(function() { - return this._connectService() - }) + return this._connectService() + }) .then(function(socket) { - this.parser = new FrameParser() - this.socket = new RiskyStream(socket) - .on('unexpectedEnd', this._socketEnded.bind(this)) - return this._readBanner(this.socket.stream) - }) + this.parser = new FrameParser() + this.socket = new RiskyStream(socket) + .on('unexpectedEnd', this._socketEnded.bind(this)) + return this._readBanner(this.socket.stream) + }) .then(function(banner) { - this.banner = banner - return this._readFrames(this.socket.stream) - }) + this.banner = banner + return this._readFrames(this.socket.stream) + }) .then(function() { - this.runningState = FrameProducer.STATE_STARTED - this.emit('start') - }) + this.runningState = FrameProducer.STATE_STARTED + this.emit('start') + }) .catch(Promise.CancellationError, function() { - return this._stop() - }) + return this._stop() + }) .catch(function(err) { - return this._stop().finally(function() { - this.failCounter.inc() - this.grabber = 'minicap-apk' + return this._stop().finally(function() { + this.failCounter.inc() + this.grabber = 'minicap-apk' + }) }) - }) .finally(function() { - this._ensureState() - }) + this._ensureState() + }) } } else { @@ -137,415 +137,415 @@ export default syrup.serial() setImmediate(this._ensureState.bind(this)) } break + } + } + FrameProducer.prototype.start = function() { + log.info('Requesting frame producer to start') + this.desiredState.push(FrameProducer.STATE_STARTED) + this._ensureState() } - } - FrameProducer.prototype.start = function() { - log.info('Requesting frame producer to start') - this.desiredState.push(FrameProducer.STATE_STARTED) - this._ensureState() - } - FrameProducer.prototype.stop = function() { - log.info('Requesting frame producer to stop') - this.desiredState.push(FrameProducer.STATE_STOPPED) - this._ensureState() - } - FrameProducer.prototype.restart = function() { - switch (this.runningState) { + FrameProducer.prototype.stop = function() { + log.info('Requesting frame producer to stop') + this.desiredState.push(FrameProducer.STATE_STOPPED) + this._ensureState() + } + FrameProducer.prototype.restart = function() { + switch (this.runningState) { case FrameProducer.STATE_STARTED: case FrameProducer.STATE_STARTING: this.desiredState.push(FrameProducer.STATE_STOPPED) this.desiredState.push(FrameProducer.STATE_STARTED) this._ensureState() break + } } - } - FrameProducer.prototype.updateRotation = function(rotation) { - if (this.frameConfig.rotation === rotation) { - log.info('Keeping %d as current frame producer rotation', rotation) - return + FrameProducer.prototype.updateRotation = function(rotation) { + if (this.frameConfig.rotation === rotation) { + log.info('Keeping %d as current frame producer rotation', rotation) + return + } + log.info('Setting frame producer rotation to %d', rotation) + this.frameConfig.rotation = rotation + this._configChanged() + } + FrameProducer.prototype.changeQuality = function(newQuality) { + log.info('Setting frame producer quality to %d', newQuality) + this.frameConfig.quality = newQuality + this._configChanged() } - log.info('Setting frame producer rotation to %d', rotation) - this.frameConfig.rotation = rotation - this._configChanged() - } - FrameProducer.prototype.changeQuality = function(newQuality) { - log.info('Setting frame producer quality to %d', newQuality) - this.frameConfig.quality = newQuality - this._configChanged() - } - FrameProducer.prototype.updateProjection = function(width, height) { - if (this.frameConfig.virtualWidth === width && + FrameProducer.prototype.updateProjection = function(width, height) { + if (this.frameConfig.virtualWidth === width && this.frameConfig.virtualHeight === height) { - log.info('Keeping %dx%d as current frame producer projection', width, height) - return + log.info('Keeping %dx%d as current frame producer projection', width, height) + return + } + log.info('Setting frame producer projection to %dx%d', width, height) + this.frameConfig.virtualWidth = width + this.frameConfig.virtualHeight = height + this._configChanged() } - log.info('Setting frame producer projection to %dx%d', width, height) - this.frameConfig.virtualWidth = width - this.frameConfig.virtualHeight = height - this._configChanged() - } - FrameProducer.prototype.nextFrame = function() { - var frame = null - var chunk - if (this.parser) { - while ((frame = this.parser.nextFrame()) === null) { - chunk = this.socket.stream.read() - if (chunk) { - this.parser.push(chunk) - } - else { - this.readable = false - break + FrameProducer.prototype.nextFrame = function() { + var frame = null + var chunk + if (this.parser) { + while ((frame = this.parser.nextFrame()) === null) { + chunk = this.socket.stream.read() + if (chunk) { + this.parser.push(chunk) + } + else { + this.readable = false + break + } } } + return frame } - return frame - } - FrameProducer.prototype.needFrame = function() { - this.needsReadable = true - this._maybeEmitReadable() - } - FrameProducer.prototype._configChanged = function() { - this.restart() - } - FrameProducer.prototype._socketEnded = function() { - log.warn('Connection to minicap ended unexpectedly') - this.failCounter.inc() - this.restart() - } - FrameProducer.prototype._outputEnded = function() { - log.warn('Shell keeping minicap running ended unexpectedly') - this.failCounter.inc() - this.restart() - } - FrameProducer.prototype._failLimitExceeded = function(limit, time) { - this._stop() - this.failed = true - this.emit('error', new Error(util.format('Failed more than %d times in %dms', limit, time))) - } - FrameProducer.prototype._startService = function() { - log.info('Launching screen service %s', this.grabber) - if (options.screenFrameRate <= 0.0) { - return minicap.run(this.grabber, util.format('-S -Q %d -P %s', this.frameConfig.quality, this.frameConfig.toString())) - .timeout(10000) + FrameProducer.prototype.needFrame = function() { + this.needsReadable = true + this._maybeEmitReadable() } - else { - return minicap.run(this.grabber, util.format('-S -r %d -Q %d -P %s', options.screenFrameRate, this.frameConfig.quality, this.frameConfig.toString())) - .timeout(10000) + FrameProducer.prototype._configChanged = function() { + this.restart() } - } - FrameProducer.prototype._readOutput = function(out) { - out.pipe(split()).on('data', function(line) { - var trimmed = line.toString().trim() - if (trimmed === '') { - return + FrameProducer.prototype._socketEnded = function() { + log.warn('Connection to minicap ended unexpectedly') + this.failCounter.inc() + this.restart() + } + FrameProducer.prototype._outputEnded = function() { + log.warn('Shell keeping minicap running ended unexpectedly') + this.failCounter.inc() + this.restart() + } + FrameProducer.prototype._failLimitExceeded = function(limit, time) { + this._stop() + this.failed = true + this.emit('error', new Error(util.format('Failed more than %d times in %dms', limit, time))) + } + FrameProducer.prototype._startService = function() { + log.info('Launching screen service %s', this.grabber) + if (options.screenFrameRate <= 0.0) { + return minicap.run(this.grabber, util.format('-S -Q %d -P %s', this.frameConfig.quality, this.frameConfig.toString())) + .timeout(10000) } - if (/ERROR/.test(line)) { - log.fatal('minicap error: "%s"', line) - return lifecycle.fatal() + else { + return minicap.run(this.grabber, util.format('-S -r %d -Q %d -P %s', options.screenFrameRate, this.frameConfig.quality, this.frameConfig.toString())) + .timeout(10000) } - var match = /^PID: (\d+)$/.exec(line) - if (match) { - this.pid = Number(match[1]) - this.emit('pid', this.pid) + } + FrameProducer.prototype._readOutput = function(out) { + out.pipe(split()).on('data', function(line) { + var trimmed = line.toString().trim() + if (trimmed === '') { + return + } + if (/ERROR/.test(line)) { + log.fatal('minicap error: "%s"', line) + return lifecycle.fatal() + } + var match = /^PID: (\d+)$/.exec(line) + if (match) { + this.pid = Number(match[1]) + this.emit('pid', this.pid) + } + log.info('minicap says: "%s"', line) + }.bind(this)) + } + FrameProducer.prototype._waitForPid = function() { + if (this.pid > 0) { + return Promise.resolve(this.pid) } - log.info('minicap says: "%s"', line) - }.bind(this)) - } - FrameProducer.prototype._waitForPid = function() { - if (this.pid > 0) { - return Promise.resolve(this.pid) + var pidListener + return new Promise(function(resolve) { + this.on('pid', pidListener = resolve) + }.bind(this)).bind(this) + .timeout(5000) + .finally(function() { + this.removeListener('pid', pidListener) + }) } - var pidListener - return new Promise(function(resolve) { - this.on('pid', pidListener = resolve) - }.bind(this)).bind(this) - .timeout(5000) - .finally(function() { - this.removeListener('pid', pidListener) - }) - } - FrameProducer.prototype._connectService = function() { - function tryConnect(times, delay) { - return adb.getDevice(options.serial).openLocal('localabstract:minicap') - .then(function(out) { - return out - }) - .catch(function(err) { - if (/closed/.test(err.message) && times > 1) { - return Promise.delay(delay) - .then(function() { - log.info('Retrying connect to minicap service') - return tryConnect(times - 1, delay + 100) // non exp, if need exponential use - delay * 2 + FrameProducer.prototype._connectService = function() { + function tryConnect(times, delay) { + return adb.getDevice(options.serial).openLocal('localabstract:minicap') + .then(function(out) { + return out }) - } - return Promise.reject(err) - }) + .catch(function(err) { + if (/closed/.test(err.message) && times > 1) { + return Promise.delay(delay) + .then(function() { + log.info('Retrying connect to minicap service') + return tryConnect(times - 1, delay + 100) // non exp, if need exponential use - delay * 2 + }) + } + return Promise.reject(err) + }) + } + log.info('Connecting to minicap service') + return tryConnect(10, 100) } - log.info('Connecting to minicap service') - return tryConnect(10, 100) - } - FrameProducer.prototype._stop = function() { - return this._disconnectService(this.socket).bind(this) - .timeout(2000) - .then(function() { - return this._stopService(this.output).timeout(10000) - }) - .then(function() { - this.runningState = FrameProducer.STATE_STOPPED - this.emit('stop') - }) - .catch(function(err) { - // In practice we _should_ never get here due to _stopService() - // being quite aggressive. But if we do, well... assume it - // stopped anyway for now. - this.runningState = FrameProducer.STATE_STOPPED - this.emit('error', err) - this.emit('stop') - }) - .finally(function() { - this.output = null - this.socket = null - this.pid = -1 - this.banner = null - this.parser = null - }) - } - FrameProducer.prototype._disconnectService = function(socket) { - log.info('Disconnecting from minicap service') - if (!socket || socket.ended) { - return Promise.resolve(true) + FrameProducer.prototype._stop = function() { + return this._disconnectService(this.socket).bind(this) + .timeout(2000) + .then(function() { + return this._stopService(this.output).timeout(10000) + }) + .then(function() { + this.runningState = FrameProducer.STATE_STOPPED + this.emit('stop') + }) + .catch(function(err) { + // In practice we _should_ never get here due to _stopService() + // being quite aggressive. But if we do, well... assume it + // stopped anyway for now. + this.runningState = FrameProducer.STATE_STOPPED + this.emit('error', err) + this.emit('stop') + }) + .finally(function() { + this.output = null + this.socket = null + this.pid = -1 + this.banner = null + this.parser = null + }) } - socket.stream.removeListener('readable', this.readableListener) - var endListener - return new Promise(function(resolve) { - socket.on('end', endListener = function() { - resolve(true) + FrameProducer.prototype._disconnectService = function(socket) { + log.info('Disconnecting from minicap service') + if (!socket || socket.ended) { + return Promise.resolve(true) + } + socket.stream.removeListener('readable', this.readableListener) + var endListener + return new Promise(function(resolve) { + socket.on('end', endListener = function() { + resolve(true) + }) + socket.stream.resume() + socket.end() }) - socket.stream.resume() - socket.end() - }) - .finally(function() { - socket.removeListener('end', endListener) - }) - } - FrameProducer.prototype._stopService = function(output) { - log.info('Stopping minicap service') - if (!output || output.ended) { - return Promise.resolve(true) + .finally(function() { + socket.removeListener('end', endListener) + }) } - var pid = this.pid - function kill(signal) { - if (pid <= 0) { - return Promise.reject(new Error('Minicap service pid is unknown')) + FrameProducer.prototype._stopService = function(output) { + log.info('Stopping minicap service') + if (!output || output.ended) { + return Promise.resolve(true) } - var signum = { - SIGTERM: -15 - , SIGKILL: -9 - }[signal] - log.info('Sending %s to minicap', signal) - return Promise.all([ - output.waitForEnd() - , adb.getDevice(options.serial).shell(['kill', signum, pid]) - .then(adbkit.Adb.util.readAll) - .return(true) - ]) - .timeout(2000) + var pid = this.pid + function kill(signal) { + if (pid <= 0) { + return Promise.reject(new Error('Minicap service pid is unknown')) + } + var signum = { + SIGTERM: -15 + , SIGKILL: -9 + }[signal] + log.info('Sending %s to minicap', signal) + return Promise.all([ + output.waitForEnd() + , adb.getDevice(options.serial).shell(['kill', signum, pid]) + .then(adbkit.Adb.util.readAll) + .return(true) + ]) + .timeout(2000) + } + function kindKill() { + return kill('SIGTERM') + } + function forceKill() { + return kill('SIGKILL') + } + function forceEnd() { + log.info('Ending minicap I/O as a last resort') + output.end() + return Promise.resolve(true) + } + return kindKill() + .catch(Promise.TimeoutError, forceKill) + .catch(forceEnd) } - function kindKill() { - return kill('SIGTERM') + FrameProducer.prototype._readBanner = function(socket) { + log.info('Reading minicap banner') + return bannerutil.read(socket).timeout(2000) } - function forceKill() { - return kill('SIGKILL') + FrameProducer.prototype._readFrames = function(socket) { + this.needsReadable = true + socket.on('readable', this.readableListener) + // We may already have data pending. Let the user know they should + // at least attempt to read frames now. + this.readableListener() } - function forceEnd() { - log.info('Ending minicap I/O as a last resort') - output.end() - return Promise.resolve(true) + FrameProducer.prototype._maybeEmitReadable = function() { + if (this.readable && this.needsReadable) { + this.needsReadable = false + this.emit('readable') + } } - return kindKill() - .catch(Promise.TimeoutError, forceKill) - .catch(forceEnd) - } - FrameProducer.prototype._readBanner = function(socket) { - log.info('Reading minicap banner') - return bannerutil.read(socket).timeout(2000) - } - FrameProducer.prototype._readFrames = function(socket) { - this.needsReadable = true - socket.on('readable', this.readableListener) - // We may already have data pending. Let the user know they should - // at least attempt to read frames now. - this.readableListener() - } - FrameProducer.prototype._maybeEmitReadable = function() { - if (this.readable && this.needsReadable) { - this.needsReadable = false - this.emit('readable') + FrameProducer.prototype._readableListener = function() { + this.readable = true + this._maybeEmitReadable() } - } - FrameProducer.prototype._readableListener = function() { - this.readable = true - this._maybeEmitReadable() - } - function createServer() { - log.info('Starting WebSocket server on port %d', screenOptions.publicPort) - var wss = new WebSocket.Server({ - port: screenOptions.publicPort - , perMessageDeflate: false - }) - var listeningListener, errorListener - return new Promise(function(resolve, reject) { - listeningListener = function() { - return resolve(wss) - } - errorListener = function(err) { - return reject(err) - } - wss.on('listening', listeningListener) - wss.on('error', errorListener) - }) - .finally(function() { - wss.removeListener('listening', listeningListener) - wss.removeListener('error', errorListener) - }) - } - return createServer() - .then(function(wss) { - log.info('creating FrameProducer: %s', options.screenGrabber) - var frameProducer = new FrameProducer(new FrameConfig(display.properties, display.properties, options.screenJpegQuality)) - var broadcastSet = frameProducer.broadcastSet = new BroadcastSet() - broadcastSet.on('nonempty', function() { - frameProducer.start() - }) - broadcastSet.on('empty', function() { - frameProducer.stop() - }) - broadcastSet.on('insert', function(id) { - // If two clients join a session in the middle, one of them - // may not release the initial size because the projection - // doesn't necessarily change, and the producer doesn't Getting - // restarted. Therefore we have to call onStart() manually - // if the producer is already up and running. - switch (frameProducer.runningState) { - case FrameProducer.STATE_STARTED: - broadcastSet.get(id).onStart(frameProducer) - break - } - }) - display.on('rotationChange', function(newRotation) { - frameProducer.updateRotation(newRotation) - }) - router.on(wire.ChangeQualityMessage, function(channel, message) { - frameProducer.changeQuality(message.quality) - }) - frameProducer.on('start', function() { - broadcastSet.keys().map(function(id) { - return broadcastSet.get(id).onStart(frameProducer) + function createServer() { + log.info('Starting WebSocket server on port %d', screenOptions.publicPort) + var wss = new WebSocket.Server({ + port: screenOptions.publicPort + , perMessageDeflate: false }) - }) - frameProducer.on('readable', function next() { - var frame = frameProducer.nextFrame() - if (frame) { - Promise.settle([broadcastSet.keys().map(function(id) { - return broadcastSet.get(id).onFrame(frame) - })]).then(next) - } - else { - frameProducer.needFrame() - } - }) - frameProducer.on('error', function(err) { - log.fatal('Frame producer had an error', err.stack) - lifecycle.fatal() - }) - wss.on('connection', function(ws) { - var id = uuid.v4() - var pingTimer - function send(message, options) { - return new Promise(function(resolve, reject) { - switch (ws.readyState) { - case WebSocket.OPENING: + var listeningListener, errorListener + return new Promise(function(resolve, reject) { + listeningListener = function() { + return resolve(wss) + } + errorListener = function(err) { + return reject(err) + } + wss.on('listening', listeningListener) + wss.on('error', errorListener) + }) + .finally(function() { + wss.removeListener('listening', listeningListener) + wss.removeListener('error', errorListener) + }) + } + return createServer() + .then(function(wss) { + log.info('creating FrameProducer: %s', options.screenGrabber) + var frameProducer = new FrameProducer(new FrameConfig(display.properties, display.properties, options.screenJpegQuality)) + var broadcastSet = frameProducer.broadcastSet = new BroadcastSet() + broadcastSet.on('nonempty', function() { + frameProducer.start() + }) + broadcastSet.on('empty', function() { + frameProducer.stop() + }) + broadcastSet.on('insert', function(id) { + // If two clients join a session in the middle, one of them + // may not release the initial size because the projection + // doesn't necessarily change, and the producer doesn't Getting + // restarted. Therefore we have to call onStart() manually + // if the producer is already up and running. + switch (frameProducer.runningState) { + case FrameProducer.STATE_STARTED: + broadcastSet.get(id).onStart(frameProducer) + break + } + }) + display.on('rotationChange', function(newRotation) { + frameProducer.updateRotation(newRotation) + }) + router.on(wire.ChangeQualityMessage, function(channel, message) { + frameProducer.changeQuality(message.quality) + }) + frameProducer.on('start', function() { + broadcastSet.keys().map(function(id) { + return broadcastSet.get(id).onStart(frameProducer) + }) + }) + frameProducer.on('readable', function next() { + var frame = frameProducer.nextFrame() + if (frame) { + Promise.settle([broadcastSet.keys().map(function(id) { + return broadcastSet.get(id).onFrame(frame) + })]).then(next) + } + else { + frameProducer.needFrame() + } + }) + frameProducer.on('error', function(err) { + log.fatal('Frame producer had an error', err.stack) + lifecycle.fatal() + }) + wss.on('connection', function(ws) { + var id = uuid.v4() + var pingTimer + function send(message, options) { + return new Promise(function(resolve, reject) { + switch (ws.readyState) { + case WebSocket.OPENING: // This should never happen. - log.warn('Unable to send to OPENING client "%s"', id) - break - case WebSocket.OPEN: + log.warn('Unable to send to OPENING client "%s"', id) + break + case WebSocket.OPEN: // This is what SHOULD happen. - ws.send(message, options, function(err) { - return err ? reject(err) : resolve() - }) - break - case WebSocket.CLOSING: + ws.send(message, options, function(err) { + return err ? reject(err) : resolve() + }) + break + case WebSocket.CLOSING: // Ok, a 'close' event should remove the client from the set // soon. - break - case WebSocket.CLOSED: + break + case WebSocket.CLOSED: // This should never happen. - log.warn('Unable to send to CLOSED client "%s"', id) - clearInterval(pingTimer) - broadcastSet.remove(id) - break + log.warn('Unable to send to CLOSED client "%s"', id) + clearInterval(pingTimer) + broadcastSet.remove(id) + break + } + }) + } + function wsStartNotifier() { + return send(util.format('start %s', JSON.stringify(frameProducer.banner))) } + function wsPingNotifier() { + return send('ping') + } + function wsFrameNotifier(frame) { + return send(frame, { + binary: true + }) + } + // Sending a ping message every now and then makes sure that + // reverse proxies like nginx don't time out the connection [1]. + // + // [1] http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_read_timeout + pingTimer = setInterval(wsPingNotifier, 10 * 60000) // options.screenPingInterval + ws.on('message', function(data) { + var match = /^(on|off|(size) ([0-9]+)x([0-9]+))$/.exec(data) + if (match) { + switch (match[2] || match[1]) { + case 'on': + broadcastSet.insert(id, { + onStart: wsStartNotifier + , onFrame: wsFrameNotifier + }) + break + case 'off': + broadcastSet.remove(id) + // Keep pinging even when the screen is off. + break + case 'size': + frameProducer.updateProjection(Number(match[3]), Number(match[4])) + break + } + } + }) + ws.on('close', function() { + clearInterval(pingTimer) + broadcastSet.remove(id) + }) + if (options.needScrcpy) { + log.info(`Scrcpy client has gotten for device s/n ${options.serial}`) + scrcpyClient.on('rawData', (data) => { + console.log(`Data: ${data}`) + send(data, {binary: true}) + }) + } + ws.on('error', function(e) { + clearInterval(pingTimer) + broadcastSet.remove(id) + }) }) - } - function wsStartNotifier() { - return send(util.format('start %s', JSON.stringify(frameProducer.banner))) - } - function wsPingNotifier() { - return send('ping') - } - function wsFrameNotifier(frame) { - return send(frame, { - binary: true + lifecycle.observe(function() { + wss.close() }) - } - // Sending a ping message every now and then makes sure that - // reverse proxies like nginx don't time out the connection [1]. - // - // [1] http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_read_timeout - pingTimer = setInterval(wsPingNotifier, 10 * 60000) // options.screenPingInterval - ws.on('message', function(data) { - var match = /^(on|off|(size) ([0-9]+)x([0-9]+))$/.exec(data) - if (match) { - switch (match[2] || match[1]) { - case 'on': - broadcastSet.insert(id, { - onStart: wsStartNotifier - , onFrame: wsFrameNotifier - }) - break - case 'off': - broadcastSet.remove(id) - // Keep pinging even when the screen is off. - break - case 'size': - frameProducer.updateProjection(Number(match[3]), Number(match[4])) - break - } - } - }) - ws.on('close', function() { - clearInterval(pingTimer) - broadcastSet.remove(id) - }) - if (options.needScrcpy) { - log.info(`Scrcpy client has gotten for device s/n ${options.serial}`) - scrcpyClient.on('rawData', (data) => { - console.log(`Data: ${data}`) - send(data, {binary: true}) + lifecycle.observe(function() { + frameProducer.stop() }) - } - ws.on('error', function(e) { - clearInterval(pingTimer) - broadcastSet.remove(id) + return frameProducer }) - }) - lifecycle.observe(function() { - wss.close() - }) - lifecycle.observe(function() { - frameProducer.stop() - }) - return frameProducer }) -}) diff --git a/lib/units/device/plugins/screen/util/banner.js b/lib/units/device/plugins/screen/util/banner.js index ef2b678c70..efd282ac74 100644 --- a/lib/units/device/plugins/screen/util/banner.js +++ b/lib/units/device/plugins/screen/util/banner.js @@ -24,64 +24,64 @@ export const read = function parseBanner(out) { for (var cursor = 0, len = chunk.length; cursor < len;) { if (readBannerBytes < needBannerBytes) { switch (readBannerBytes) { - case 0: - // version - banner.version = chunk[cursor] - break - case 1: - // length - banner.length = needBannerBytes = chunk[cursor] - break - case 2: - case 3: - case 4: - case 5: - // pid - banner.pid += + case 0: + // version + banner.version = chunk[cursor] + break + case 1: + // length + banner.length = needBannerBytes = chunk[cursor] + break + case 2: + case 3: + case 4: + case 5: + // pid + banner.pid += (chunk[cursor] << ((readBannerBytes - 2) * 8)) >>> 0 - break - case 6: - case 7: - case 8: - case 9: - // real width - banner.realWidth += + break + case 6: + case 7: + case 8: + case 9: + // real width + banner.realWidth += (chunk[cursor] << ((readBannerBytes - 6) * 8)) >>> 0 - break - case 10: - case 11: - case 12: - case 13: - // real height - banner.realHeight += + break + case 10: + case 11: + case 12: + case 13: + // real height + banner.realHeight += (chunk[cursor] << ((readBannerBytes - 10) * 8)) >>> 0 - break - case 14: - case 15: - case 16: - case 17: - // virtual width - banner.virtualWidth += + break + case 14: + case 15: + case 16: + case 17: + // virtual width + banner.virtualWidth += (chunk[cursor] << ((readBannerBytes - 14) * 8)) >>> 0 - break - case 18: - case 19: - case 20: - case 21: - // virtual height - banner.virtualHeight += + break + case 18: + case 19: + case 20: + case 21: + // virtual height + banner.virtualHeight += (chunk[cursor] << ((readBannerBytes - 18) * 8)) >>> 0 - break - case 22: - // orientation - banner.orientation += chunk[cursor] * 90 - break - case 23: - // quirks - banner.quirks.dumb = (chunk[cursor] & 1) === 1 - banner.quirks.alwaysUpright = (chunk[cursor] & 2) === 2 - banner.quirks.tear = (chunk[cursor] & 4) === 4 - break + break + case 22: + // orientation + banner.orientation += chunk[cursor] * 90 + break + case 23: + // quirks + banner.quirks.dumb = (chunk[cursor] & 1) === 1 + banner.quirks.alwaysUpright = (chunk[cursor] & 2) === 2 + banner.quirks.tear = (chunk[cursor] & 4) === 4 + break } cursor += 1 readBannerBytes += 1 @@ -99,6 +99,6 @@ export const read = function parseBanner(out) { out.on('readable', tryRead) }) .finally(function() { - out.removeListener('readable', tryRead) - }) + out.removeListener('readable', tryRead) + }) } diff --git a/lib/units/device/plugins/sd.js b/lib/units/device/plugins/sd.js index 62f9a86c7e..6e3d5bbfee 100644 --- a/lib/units/device/plugins/sd.js +++ b/lib/units/device/plugins/sd.js @@ -10,24 +10,24 @@ export default syrup.serial() .dependency(router) .dependency(push) .define(function(options, service, router, push) { - var log = logger.createLogger('device:plugins:sd') - router.on(wire.SdStatusMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - log.info('Getting SD card status') - service.getSdStatus(message) - .timeout(30000) - .then(function(mounted) { - push.send([ - channel - , reply.okay(mounted ? 'sd_mounted' : 'sd_unmounted') - ]) - }) - .catch(function(err) { - log.error('Getting SD card Status', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) + var log = logger.createLogger('device:plugins:sd') + router.on(wire.SdStatusMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + log.info('Getting SD card status') + service.getSdStatus(message) + .timeout(30000) + .then(function(mounted) { + push.send([ + channel + , reply.okay(mounted ? 'sd_mounted' : 'sd_unmounted') + ]) + }) + .catch(function(err) { + log.error('Getting SD card Status', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) }) -}) diff --git a/lib/units/device/plugins/service.js b/lib/units/device/plugins/service.js index b41b3ad869..d501c45b8b 100644 --- a/lib/units/device/plugins/service.js +++ b/lib/units/device/plugins/service.js @@ -10,7 +10,7 @@ import * as streamutil from '../../../util/streamutil.js' import logger from '../../../util/logger.js' import * as ms from '../../../wire/messagestream.js' import lifecycle from '../../../util/lifecycle.js' -import apiutil from '../../../util/apiutil.js' +import * as apiutil from '../../../util/apiutil.js' import adb from '../support/adb.js' import router from '../../base-device/support/router.js' import push from '../../base-device/support/push.js' @@ -36,90 +36,90 @@ export default syrup.serial() .dependency(sdk) .dependency(service) .define(function(options, adb, router, push, sdk, apk) { - var log = logger.createLogger('device:plugins:service') - var messageResolver = new MessageResolver() - var plugin = new events.EventEmitter() - var agent = { - socket: null - , writer: null - , sock: 'localabstract:stfagent' - } - var service = { - socket: null - , writer: null - , reader: null - , sock: 'localabstract:stfservice' - } - function stopAgent() { - return devutil.killProcsByComm(adb, options.serial, 'stf.agent', 'stf.agent') - } - function callService(intent) { - var startServiceCmd = (sdk.level < 26) ? 'startservice' : 'start-foreground-service' - log.info('using \'%s\' command for API %s', startServiceCmd, sdk.level) - return adb.getDevice(options.serial).shell(util.format('am %s --user 0 %s', startServiceCmd, intent)) - .then(function(out) { - return streamutil.findLine(out, /^Error/) - .finally(function() { - out.end() - }) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(line) { - if (line.indexOf('--user') !== -1) { - let command = util.format('am %s %s', startServiceCmd, intent) - log.info('Stating service with command ' + command) - return adb.getDevice(options.serial).shell(command) - .then(function() { - return streamutil.findLine(out, /^Error/) - .finally(function() { + var log = logger.createLogger('device:plugins:service') + var messageResolver = new MessageResolver() + var plugin = new events.EventEmitter() + var agent = { + socket: null + , writer: null + , sock: 'localabstract:stfagent' + } + var service = { + socket: null + , writer: null + , reader: null + , sock: 'localabstract:stfservice' + } + function stopAgent() { + return devutil.killProcsByComm(adb, options.serial, 'stf.agent', 'stf.agent') + } + function callService(intent) { + var startServiceCmd = (sdk.level < 26) ? 'startservice' : 'start-foreground-service' + log.info('using \'%s\' command for API %s', startServiceCmd, sdk.level) + return adb.getDevice(options.serial).shell(util.format('am %s --user 0 %s', startServiceCmd, intent)) + .then(function(out) { + return streamutil.findLine(out, /^Error/) + .finally(function() { out.end() }) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(line) { - throw new Error(util.format('Service had an error: "%s"', line)) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(line) { + if (line.indexOf('--user') !== -1) { + let command = util.format('am %s %s', startServiceCmd, intent) + log.info('Stating service with command ' + command) + return adb.getDevice(options.serial).shell(command) + .then(function() { + return streamutil.findLine(out, /^Error/) + .finally(function() { + out.end() + }) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(line) { + throw new Error(util.format('Service had an error: "%s"', line)) + }) + .catch(streamutil.NoSuchLineError, function() { + return true + }) + }) + } + else { + throw new Error(util.format('Service had an error: "%s"', line)) + } }) - .catch(streamutil.NoSuchLineError, function() { + .catch(streamutil.NoSuchLineError, function() { return true }) + }) + } + function prepareForServiceDeath(conn) { + function endListener() { + var startTime = Date.now() + log.important('Service connection ended, attempting to relaunch') + /* eslint no-use-before-define: 0 */ + openService() + .timeout(5000) + .then(function() { + log.important('Service relaunched in %dms', Date.now() - startTime) }) - } - else { - throw new Error(util.format('Service had an error: "%s"', line)) - } - }) - .catch(streamutil.NoSuchLineError, function() { - return true - }) - }) - } - function prepareForServiceDeath(conn) { - function endListener() { - var startTime = Date.now() - log.important('Service connection ended, attempting to relaunch') - /* eslint no-use-before-define: 0 */ - openService() - .timeout(5000) - .then(function() { - log.important('Service relaunched in %dms', Date.now() - startTime) - }) - .catch(function(err) { - log.fatal('Service connection could not be relaunched', err.stack) + .catch(function(err) { + log.fatal('Service connection could not be relaunched', err.stack) + lifecycle.fatal() + }) + } + conn.once('end', endListener) + conn.on('error', function(err) { + log.fatal('Service connection had an error', err.stack) lifecycle.fatal() }) } - conn.once('end', endListener) - conn.on('error', function(err) { - log.fatal('Service connection had an error', err.stack) - lifecycle.fatal() - }) - } - function handleEnvelope(data) { - var envelope = apk.wire.Envelope.decode(data) - var message - if (envelope.id !== null) { - messageResolver.resolve(envelope.id, envelope.message) - } - else { - switch (envelope.type) { + function handleEnvelope(data) { + var envelope = apk.wire.Envelope.decode(data) + var message + if (envelope.id !== null) { + messageResolver.resolve(envelope.id, envelope.message) + } + else { + switch (envelope.type) { case apk.wire.MessageType.EVENT_AIRPLANE_MODE: message = apk.wire.AirplaneModeEvent.decode(envelope.message) push.send([ @@ -164,511 +164,510 @@ export default syrup.serial() ]) plugin.emit('rotationChange', message) break + } } } - } - // The APK should be up to date at this point. If it was reinstalled, the - // service should have been automatically stopped while it was happening. - // So, we should be good to go. - function openService() { - log.info('Launching service') - return callService(util.format("-a '%s' -n '%s'", apk.startIntent.action, apk.startIntent.component)) - .then(function() { - return devutil.waitForLocalSocket(adb, options.serial, service.sock) - .timeout(15000) - }) - .then(function(conn) { - service.socket = conn - service.reader = conn.pipe(new ms.DelimitedStream()) - service.reader.on('data', handleEnvelope) - service.writer = new ms.DelimitingStream() - service.writer.pipe(conn) - return prepareForServiceDeath(conn) - }) - .then(function() { - devutil.executeShellCommand(adb, options.serial, 'settings put system screen_brightness 0') - devutil.executeShellCommand(adb, options.serial, 'settings put system screen_brightness_mode 0') - devutil.executeShellCommand(adb, options.serial, 'input keyevent 26') - }) - } - function prepareForAgentDeath(conn) { - function endListener() { - var startTime = Date.now() - log.important('Agent connection ended, attempting to relaunch') - openService() - .timeout(5000) + // The APK should be up to date at this point. If it was reinstalled, the + // service should have been automatically stopped while it was happening. + // So, we should be good to go. + function openService() { + log.info('Launching service') + return callService(util.format("-a '%s' -n '%s'", apk.startIntent.action, apk.startIntent.component)) .then(function() { - log.important('Agent relaunched in %dms', Date.now() - startTime) - }) - .catch(function(err) { - log.fatal('Agent connection could not be relaunched', err.stack) + return devutil.waitForLocalSocket(adb, options.serial, service.sock) + .timeout(15000) + }) + .then(function(conn) { + service.socket = conn + service.reader = conn.pipe(new ms.DelimitedStream()) + service.reader.on('data', handleEnvelope) + service.writer = new ms.DelimitingStream() + service.writer.pipe(conn) + return prepareForServiceDeath(conn) + }) + .then(function() { + devutil.executeShellCommand(adb, options.serial, 'settings put system screen_brightness 0') + devutil.executeShellCommand(adb, options.serial, 'settings put system screen_brightness_mode 0') + devutil.executeShellCommand(adb, options.serial, 'input keyevent 26') + }) + } + function prepareForAgentDeath(conn) { + function endListener() { + var startTime = Date.now() + log.important('Agent connection ended, attempting to relaunch') + openService() + .timeout(5000) + .then(function() { + log.important('Agent relaunched in %dms', Date.now() - startTime) + }) + .catch(function(err) { + log.fatal('Agent connection could not be relaunched', err.stack) + lifecycle.fatal() + }) + } + conn.once('end', endListener) + conn.on('error', function(err) { + log.fatal('Agent connection had an error', err.stack) lifecycle.fatal() }) } - conn.once('end', endListener) - conn.on('error', function(err) { - log.fatal('Agent connection had an error', err.stack) - lifecycle.fatal() - }) - } - function openAgent() { - log.info('Launching agent') - return stopAgent() - .timeout(15000) - .then(function() { - return devutil.ensureUnusedLocalSocket(adb, options.serial, agent.sock) - .timeout(apiutil.GRPC_TIMEOUT) - }) - .then(function() { - console.log(apk) - return adb.getDevice(options.serial).shell(util.format("CLASSPATH='%s' exec app_process /system/bin '%s'", apk.path, apk.main)) - }) - .then(function(out) { - streamutil.talk(log, 'Agent says: "%s"', out) - }) - .then(function() { - return devutil.waitForLocalSocket(adb, options.serial, agent.sock) - .timeout(apiutil.GRPC_TIMEOUT) - }) - .then(function(conn) { - agent.socket = conn - agent.writer = new ms.DelimitingStream() - agent.writer.pipe(conn) - return prepareForAgentDeath(conn) - }) - } - function runAgentCommand(type, cmd) { - agent.writer.write(new apk.wire.Envelope(null, type, cmd.encodeNB()).encodeNB()) - } - function keyEvent(data) { - return runAgentCommand(apk.wire.MessageType.DO_KEYEVENT, new apk.wire.KeyEventRequest(data)) - } - plugin.type = function(text) { - devutil.executeShellCommand(adb, options.serial, "am broadcast -a ADB_INPUT_TEXT --es msg '" + text + "'") - } - plugin.paste = function(text) { - return plugin.setClipboard(text) - .delay(500) // Give it a little bit of time to settle. - .then(function() { - keyEvent({ - event: apk.wire.KeyEvent.PRESS - , keyCode: adb.Keycode.KEYCODE_V - , ctrlKey: true - }) - }) - } - plugin.copy = function() { + function openAgent() { + log.info('Launching agent') + return stopAgent() + .timeout(15000) + .then(function() { + return devutil.ensureUnusedLocalSocket(adb, options.serial, agent.sock) + .timeout(apiutil.GRPC_TIMEOUT) + }) + .then(function() { + console.log(apk) + return adb.getDevice(options.serial).shell(util.format("CLASSPATH='%s' exec app_process /system/bin '%s'", apk.path, apk.main)) + }) + .then(function(out) { + streamutil.talk(log, 'Agent says: "%s"', out) + }) + .then(function() { + return devutil.waitForLocalSocket(adb, options.serial, agent.sock) + .timeout(apiutil.GRPC_TIMEOUT) + }) + .then(function(conn) { + agent.socket = conn + agent.writer = new ms.DelimitingStream() + agent.writer.pipe(conn) + return prepareForAgentDeath(conn) + }) + } + function runAgentCommand(type, cmd) { + agent.writer.write(new apk.wire.Envelope(null, type, cmd.encodeNB()).encodeNB()) + } + function keyEvent(data) { + return runAgentCommand(apk.wire.MessageType.DO_KEYEVENT, new apk.wire.KeyEventRequest(data)) + } + plugin.type = function(text) { + devutil.executeShellCommand(adb, options.serial, "am broadcast -a ADB_INPUT_TEXT --es msg '" + text + "'") + } + plugin.paste = function(text) { + return plugin.setClipboard(text) + .delay(500) // Give it a little bit of time to settle. + .then(function() { + keyEvent({ + event: apk.wire.KeyEvent.PRESS + , keyCode: adb.Keycode.KEYCODE_V + , ctrlKey: true + }) + }) + } + plugin.copy = function() { // @TODO Not sure how to force the device to copy the current selection // yet. - return plugin.getClipboard() - } - function runServiceCommand(type, cmd) { - var resolver = Promise.defer() - var id = Math.floor(Math.random() * 0xFFFFFF) - service.writer.write(new apk.wire.Envelope(id, type, cmd.encodeNB()).encodeNB()) - return messageResolver.await(id, resolver) - } - plugin.getDisplay = function(id) { - return runServiceCommand(apk.wire.MessageType.GET_DISPLAY, new apk.wire.GetDisplayRequest(id)) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.GetDisplayResponse.decode(data) - if (response.success) { - return { - id: id - , width: response.width - , height: response.height - , xdpi: response.xdpi - , ydpi: response.ydpi - , fps: response.fps - , density: response.density - , rotation: response.rotation - , secure: response.secure - , size: Math.sqrt(Math.pow(response.width / response.xdpi, 2) + + return plugin.getClipboard() + } + function runServiceCommand(type, cmd) { + var resolver = Promise.defer() + var id = Math.floor(Math.random() * 0xFFFFFF) + service.writer.write(new apk.wire.Envelope(id, type, cmd.encodeNB()).encodeNB()) + return messageResolver.await(id, resolver) + } + plugin.getDisplay = function(id) { + return runServiceCommand(apk.wire.MessageType.GET_DISPLAY, new apk.wire.GetDisplayRequest(id)) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.GetDisplayResponse.decode(data) + if (response.success) { + return { + id: id + , width: response.width + , height: response.height + , xdpi: response.xdpi + , ydpi: response.ydpi + , fps: response.fps + , density: response.density + , rotation: response.rotation + , secure: response.secure + , size: Math.sqrt(Math.pow(response.width / response.xdpi, 2) + Math.pow(response.height / response.ydpi, 2)) - } - } - throw new Error('Unable to retrieve display information') - }) - } - plugin.wake = function() { - return runAgentCommand(apk.wire.MessageType.DO_WAKE, new apk.wire.DoWakeRequest()) - } - plugin.rotate = function(rotation) { - return runAgentCommand(apk.wire.MessageType.SET_ROTATION, new apk.wire.SetRotationRequest(rotation, true)) - } - plugin.freezeRotation = function(rotation) { - return runAgentCommand(apk.wire.MessageType.SET_ROTATION, new apk.wire.SetRotationRequest(rotation, true)) - } - plugin.thawRotation = function() { - return runAgentCommand(apk.wire.MessageType.SET_ROTATION, new apk.wire.SetRotationRequest(0, false)) - } - plugin.version = function() { - return runServiceCommand(apk.wire.MessageType.GET_VERSION, new apk.wire.GetVersionRequest()) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.GetVersionResponse.decode(data) - if (response.success) { - return response.version - } - throw new Error('Unable to retrieve version') - }) - } - plugin.unlock = function() { - return runServiceCommand(apk.wire.MessageType.SET_KEYGUARD_STATE, new apk.wire.SetKeyguardStateRequest(false)) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.SetKeyguardStateResponse.decode(data) - if (!response.success) { - throw new Error('Unable to unlock device') - } - }) - } - plugin.lock = function() { - return runServiceCommand(apk.wire.MessageType.SET_KEYGUARD_STATE, new apk.wire.SetKeyguardStateRequest(true)) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.SetKeyguardStateResponse.decode(data) - if (!response.success) { - throw new Error('Unable to lock device') - } - }) - } - plugin.acquireWakeLock = function() { - return runServiceCommand(apk.wire.MessageType.SET_WAKE_LOCK, new apk.wire.SetWakeLockRequest(true)) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.SetWakeLockResponse.decode(data) - if (!response.success) { - throw new Error('Unable to acquire WakeLock') - } - }) - } - plugin.releaseWakeLock = function() { - return runServiceCommand(apk.wire.MessageType.SET_WAKE_LOCK, new apk.wire.SetWakeLockRequest(false)) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.SetWakeLockResponse.decode(data) - if (!response.success) { - throw new Error('Unable to release WakeLock') - } - }) - } - plugin.identity = function() { - log.info('Calling Do Identify gRPC') - return runServiceCommand(apk.wire.MessageType.DO_IDENTIFY, new apk.wire.DoIdentifyRequest(options.serial)) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - let response = apk.wire.DoIdentifyResponse.decode(data) - if (!response.success) { - throw new Error('Unable to identify device') - } - }) - } - plugin.setClipboard = function(text) { - return runServiceCommand(apk.wire.MessageType.SET_CLIPBOARD, new apk.wire.SetClipboardRequest(apk.wire.ClipboardType.TEXT, text)) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.SetClipboardResponse.decode(data) - if (!response.success) { - throw new Error('Unable to set clipboard') - } - }) - } - plugin.getClipboard = function() { - return runServiceCommand(apk.wire.MessageType.GET_CLIPBOARD, new apk.wire.GetClipboardRequest(apk.wire.ClipboardType.TEXT)) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.GetClipboardResponse.decode(data) - if (response.success) { - switch (response.type) { - case apk.wire.ClipboardType.TEXT: - return response.text - } - } - throw new Error('Unable to get clipboard') - }) - } - plugin.getBrowsers = function() { - return runServiceCommand(apk.wire.MessageType.GET_BROWSERS, new apk.wire.GetBrowsersRequest()) - .timeout(15000) - .then(function(data) { - var response = apk.wire.GetBrowsersResponse.decode(data) - if (response.success) { - delete response.success - return response - } - throw new Error('Unable to get browser list') - }) - } - plugin.getMobileServices = function() { - return runServiceCommand(apk.wire.MessageType.GET_SERVICES, new apk.wire.GetServicesAvailabilityRequest()) - .timeout(15000) - .then(function(data) { - let response = apk.wire.GetServicesAvailabilityResponse.decode(data) - if (response.success) { - delete response.success - return response - } - throw new Error('Unable to get mobile services') - }) - } - plugin.getProperties = function(properties) { - return runServiceCommand(apk.wire.MessageType.GET_PROPERTIES, new apk.wire.GetPropertiesRequest(properties)) - .timeout(200000) - .then(function(data) { - let response = apk.wire.GetPropertiesResponse.decode(data) - if (response.success) { - let mapped = {} - response.properties.forEach(function(property) { - mapped[property.name] = property.value - }) - if (!mapped.imei) { - return adb.getDevice(options.serial).getProperties() - .then((props) => { - let sdk = props['ro.build.version.sdk'] - let command - if (sdk >= 24) { - // eslint-disable-next-line max-len - command = "service call iphonesubinfo 1 | awk -F \"'\" '{print $2}' | sed '1 d' | tr -d '.' | awk '{print}' ORS= | xargs echo imei:" } - else { - command = 'service call iphonesubinfo 1 | cut -c 52-66 | tr -d \'.[:space:]\' | xargs echo imei:' + } + throw new Error('Unable to retrieve display information') + }) + } + plugin.wake = function() { + return runAgentCommand(apk.wire.MessageType.DO_WAKE, new apk.wire.DoWakeRequest()) + } + plugin.rotate = function(rotation) { + return runAgentCommand(apk.wire.MessageType.SET_ROTATION, new apk.wire.SetRotationRequest(rotation, true)) + } + plugin.freezeRotation = function(rotation) { + return runAgentCommand(apk.wire.MessageType.SET_ROTATION, new apk.wire.SetRotationRequest(rotation, true)) + } + plugin.thawRotation = function() { + return runAgentCommand(apk.wire.MessageType.SET_ROTATION, new apk.wire.SetRotationRequest(0, false)) + } + plugin.version = function() { + return runServiceCommand(apk.wire.MessageType.GET_VERSION, new apk.wire.GetVersionRequest()) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.GetVersionResponse.decode(data) + if (response.success) { + return response.version + } + throw new Error('Unable to retrieve version') + }) + } + plugin.unlock = function() { + return runServiceCommand(apk.wire.MessageType.SET_KEYGUARD_STATE, new apk.wire.SetKeyguardStateRequest(false)) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.SetKeyguardStateResponse.decode(data) + if (!response.success) { + throw new Error('Unable to unlock device') + } + }) + } + plugin.lock = function() { + return runServiceCommand(apk.wire.MessageType.SET_KEYGUARD_STATE, new apk.wire.SetKeyguardStateRequest(true)) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.SetKeyguardStateResponse.decode(data) + if (!response.success) { + throw new Error('Unable to lock device') + } + }) + } + plugin.acquireWakeLock = function() { + return runServiceCommand(apk.wire.MessageType.SET_WAKE_LOCK, new apk.wire.SetWakeLockRequest(true)) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.SetWakeLockResponse.decode(data) + if (!response.success) { + throw new Error('Unable to acquire WakeLock') + } + }) + } + plugin.releaseWakeLock = function() { + return runServiceCommand(apk.wire.MessageType.SET_WAKE_LOCK, new apk.wire.SetWakeLockRequest(false)) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.SetWakeLockResponse.decode(data) + if (!response.success) { + throw new Error('Unable to release WakeLock') + } + }) + } + plugin.identity = function() { + log.info('Calling Do Identify gRPC') + return runServiceCommand(apk.wire.MessageType.DO_IDENTIFY, new apk.wire.DoIdentifyRequest(options.serial)) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + let response = apk.wire.DoIdentifyResponse.decode(data) + if (!response.success) { + throw new Error('Unable to identify device') + } + }) + } + plugin.setClipboard = function(text) { + return runServiceCommand(apk.wire.MessageType.SET_CLIPBOARD, new apk.wire.SetClipboardRequest(apk.wire.ClipboardType.TEXT, text)) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.SetClipboardResponse.decode(data) + if (!response.success) { + throw new Error('Unable to set clipboard') + } + }) + } + plugin.getClipboard = function() { + return runServiceCommand(apk.wire.MessageType.GET_CLIPBOARD, new apk.wire.GetClipboardRequest(apk.wire.ClipboardType.TEXT)) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.GetClipboardResponse.decode(data) + if (response.success) { + switch (response.type) { + case apk.wire.ClipboardType.TEXT: + return response.text } - return adb.getDevice(options.serial).shell(command) - .then((out) => { - return streamutil.findLine(out, (/^imei:/)) - .timeout(15000) - .then(function(line) { - let splitedLine = line.split('imei: ') - if (splitedLine.length > 1) { - mapped.imei = line.split('imei: ')[1] - } - else { - mapped.imei = 'secured' - } - return mapped - }) + } + throw new Error('Unable to get clipboard') + }) + } + plugin.getBrowsers = function() { + return runServiceCommand(apk.wire.MessageType.GET_BROWSERS, new apk.wire.GetBrowsersRequest()) + .timeout(15000) + .then(function(data) { + var response = apk.wire.GetBrowsersResponse.decode(data) + if (response.success) { + delete response.success + return response + } + throw new Error('Unable to get browser list') + }) + } + plugin.getMobileServices = function() { + return runServiceCommand(apk.wire.MessageType.GET_SERVICES, new apk.wire.GetServicesAvailabilityRequest()) + .timeout(15000) + .then(function(data) { + let response = apk.wire.GetServicesAvailabilityResponse.decode(data) + if (response.success) { + delete response.success + return response + } + throw new Error('Unable to get mobile services') + }) + } + plugin.getProperties = function(properties) { + return runServiceCommand(apk.wire.MessageType.GET_PROPERTIES, new apk.wire.GetPropertiesRequest(properties)) + .timeout(200000) + .then(function(data) { + let response = apk.wire.GetPropertiesResponse.decode(data) + if (response.success) { + let mapped = {} + response.properties.forEach(function(property) { + mapped[property.name] = property.value }) - .catch((e) => { - log.error(e) - log.info('setting secured imei because of error') - mapped.imei = 'secured' + if (!mapped.imei) { + return adb.getDevice(options.serial).getProperties() + .then((props) => { + let sdk = props['ro.build.version.sdk'] + let command + if (sdk >= 24) { + command = "service call iphonesubinfo 1 | awk -F \"'\" '{print $2}' | sed '1 d' | tr -d '.' | awk '{print}' ORS= | xargs echo imei:" + } + else { + command = 'service call iphonesubinfo 1 | cut -c 52-66 | tr -d \'.[:space:]\' | xargs echo imei:' + } + return adb.getDevice(options.serial).shell(command) + .then((out) => { + return streamutil.findLine(out, (/^imei:/)) + .timeout(15000) + .then(function(line) { + let splitedLine = line.split('imei: ') + if (splitedLine.length > 1) { + mapped.imei = line.split('imei: ')[1] + } + else { + mapped.imei = 'secured' + } + return mapped + }) + }) + .catch((e) => { + log.error(e) + log.info('setting secured imei because of error') + mapped.imei = 'secured' + return mapped + }) + }) + } + else { return mapped - }) - }) - } - else { - return mapped - } - } - throw new Error('Unable to get properties') - }) - } - plugin.getAccounts = function(data) { - return runServiceCommand(apk.wire.MessageType.GET_ACCOUNTS, new apk.wire.GetAccountsRequest({type: data.type})) - .timeout(15000) - .then(function(data) { - var response = apk.wire.GetAccountsResponse.decode(data) - if (response.success) { - return response.accounts - } - throw new Error('No accounts returned') - }) - } - plugin.removeAccount = function(data) { - return runServiceCommand(apk.wire.MessageType.DO_REMOVE_ACCOUNT, new apk.wire.DoRemoveAccountRequest({ - type: data.type - , account: data.account - })) - .timeout(15000) - .then(function(data) { - var response = apk.wire.DoRemoveAccountResponse.decode(data) - if (response.success) { - return true - } - throw new Error('Unable to remove account') - }) - } - plugin.addAccountMenu = function() { - return runServiceCommand(apk.wire.MessageType.DO_ADD_ACCOUNT_MENU, new apk.wire.DoAddAccountMenuRequest()) - .timeout(15000) - .then(function(data) { - var response = apk.wire.DoAddAccountMenuResponse.decode(data) - if (response.success) { - return true - } - throw new Error('Unable to show add account menu') - }) - } - plugin.cleanupBondedBluetoothDevices = function() { - return runServiceCommand(apk.wire.MessageType.DO_CLEAN_BLUETOOTH_BONDED_DEVICES, new apk.wire.DoCleanBluetoothBondedDevicesRequest()) - .timeout(15000) - .then(function(data) { - var response = apk.wire.DoCleanBluetoothBondedDevicesResponse.decode(data) - if (response.success) { - return true - } - throw new Error('Unable to clean bluetooth bonded devices') - }) - } - plugin.setRingerMode = function(mode) { - return runServiceCommand(apk.wire.MessageType.SET_RINGER_MODE, new apk.wire.SetRingerModeRequest(mode)) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.SetRingerModeResponse.decode(data) - if (!response.success) { - throw new Error('Unable to set ringer mode') - } - }) - } - plugin.getRingerMode = function() { - return runServiceCommand(apk.wire.MessageType.GET_RINGER_MODE, new apk.wire.GetRingerModeRequest()) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.GetRingerModeResponse.decode(data) - // Reflection to decode enums to their string values, otherwise - // we only get an integer - var ringerMode = apk.builder.lookup('jp.co.cyberagent.stf.proto.RingerMode') - .children[response.mode].name - if (response.success) { - return ringerMode - } - throw new Error('Unable to get ringer mode') - }) - } - plugin.setWifiEnabled = function(enabled) { - return runServiceCommand(apk.wire.MessageType.SET_WIFI_ENABLED, new apk.wire.SetWifiEnabledRequest(enabled)) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.SetWifiEnabledResponse.decode(data) - if (!response.success) { - throw new Error('Unable to set Wifi') - } - }) - } - plugin.getWifiStatus = function() { - return runServiceCommand(apk.wire.MessageType.GET_WIFI_STATUS, new apk.wire.GetWifiStatusRequest()) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.GetWifiStatusResponse.decode(data) - if (response.success) { - return response.status - } - throw new Error('Unable to get Wifi status') - }) - } - plugin.sendCommand = function(command) { - log.info('Executing shell command ' + command + ' on ' + options.serial) - devutil.executeShellCommand(adb, options.serial, command) - } - plugin.setBluetoothEnabled = function(enabled) { - return runServiceCommand(apk.wire.MessageType.SET_BLUETOOTH_ENABLED, new apk.wire.SetBluetoothEnabledRequest(enabled)) - .timeout(10000) - .then(function(data) { - var response = apk.wire.SetBluetoothEnabledResponse.decode(data) - if (!response.success) { - throw new Error('Unable to set Bluetooth') - } - }) - } - plugin.cleanBluetoothBonds = function() { - return runServiceCommand(apk.wire.MessageType.DO_CLEAN_BLUETOOTH_BONDED_DEVICES, new apk.wire.DoCleanBluetoothBondedDevicesRequest()) - .timeout(10000) - .then(function(data) { - var response = apk.wire.DoCleanBluetoothBondedDevicesResponse.decode(data) - if (!response.success) { - throw new Error('Unable to clean Bluetooth bonded devices') - } - }) - } - plugin.getBluetoothStatus = function() { - return runServiceCommand(apk.wire.MessageType.GET_BLUETOOTH_STATUS, new apk.wire.GetBluetoothStatusRequest()) - .timeout(10000) - .then(function(data) { - var response = apk.wire.GetBluetoothStatusResponse.decode(data) - if (response.success) { - return response.status - } - throw new Error('Unable to get Bluetooth status') - }) - } - plugin.getSdStatus = function() { - return runServiceCommand(apk.wire.MessageType.GET_SD_STATUS, new apk.wire.GetSdStatusRequest()) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.GetSdStatusResponse.decode(data) - if (response.success) { - return response.mounted - } - throw new Error('Unable to get SD card status') - }) - } - plugin.pressKey = function(key) { - keyEvent({event: apk.wire.KeyEvent.PRESS, keyCode: keyutil.namedKey(key)}) - return Promise.resolve(true) - } - plugin.setMasterMute = function(mode) { - return runServiceCommand(apk.wire.MessageType.SET_MASTER_MUTE, new apk.wire.SetMasterMuteRequest(mode)) - .timeout(apiutil.GRPC_TIMEOUT) - .then(function(data) { - var response = apk.wire.SetMasterMuteResponse.decode(data) - if (!response.success) { - throw new Error('Unable to set master mute') - } - }) - } - return openAgent() - .then(openService) - .then(function() { - router - .on(wire.PhysicalIdentifyMessage, function(channel) { - var reply = wireutil.reply(options.serial) - plugin.identity() - push.send([ - channel - , reply.okay() - ]) - }) - .on(wire.KeyDownMessage, function(channel, message) { - try { - keyEvent({ - event: apk.wire.KeyEvent.DOWN - , keyCode: keyutil.namedKey(message.key) + } + } + throw new Error('Unable to get properties') }) - } - catch (e) { - log.warn(e.message) - } - }) - .on(wire.KeyUpMessage, function(channel, message) { - try { - keyEvent({ - event: apk.wire.KeyEvent.UP - , keyCode: keyutil.namedKey(message.key) + } + plugin.getAccounts = function(data) { + return runServiceCommand(apk.wire.MessageType.GET_ACCOUNTS, new apk.wire.GetAccountsRequest({type: data.type})) + .timeout(15000) + .then(function(data) { + var response = apk.wire.GetAccountsResponse.decode(data) + if (response.success) { + return response.accounts + } + throw new Error('No accounts returned') }) - } - catch (e) { - log.warn(e.message) - } - }) - .on(wire.KeyPressMessage, function(channel, message) { - try { - keyEvent({ - event: apk.wire.KeyEvent.PRESS - , keyCode: keyutil.namedKey(message.key) + } + plugin.removeAccount = function(data) { + return runServiceCommand(apk.wire.MessageType.DO_REMOVE_ACCOUNT, new apk.wire.DoRemoveAccountRequest({ + type: data.type + , account: data.account + })) + .timeout(15000) + .then(function(data) { + var response = apk.wire.DoRemoveAccountResponse.decode(data) + if (response.success) { + return true + } + throw new Error('Unable to remove account') }) - } - catch (e) { - log.warn(e.message) - } - }) - .on(wire.TypeMessage, function(channel, message) { - plugin.type(message.text) - }) - .on(wire.RotateMessage, function(channel, message) { - plugin.rotate(message.rotation) - }) + } + plugin.addAccountMenu = function() { + return runServiceCommand(apk.wire.MessageType.DO_ADD_ACCOUNT_MENU, new apk.wire.DoAddAccountMenuRequest()) + .timeout(15000) + .then(function(data) { + var response = apk.wire.DoAddAccountMenuResponse.decode(data) + if (response.success) { + return true + } + throw new Error('Unable to show add account menu') + }) + } + plugin.cleanupBondedBluetoothDevices = function() { + return runServiceCommand(apk.wire.MessageType.DO_CLEAN_BLUETOOTH_BONDED_DEVICES, new apk.wire.DoCleanBluetoothBondedDevicesRequest()) + .timeout(15000) + .then(function(data) { + var response = apk.wire.DoCleanBluetoothBondedDevicesResponse.decode(data) + if (response.success) { + return true + } + throw new Error('Unable to clean bluetooth bonded devices') + }) + } + plugin.setRingerMode = function(mode) { + return runServiceCommand(apk.wire.MessageType.SET_RINGER_MODE, new apk.wire.SetRingerModeRequest(mode)) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.SetRingerModeResponse.decode(data) + if (!response.success) { + throw new Error('Unable to set ringer mode') + } + }) + } + plugin.getRingerMode = function() { + return runServiceCommand(apk.wire.MessageType.GET_RINGER_MODE, new apk.wire.GetRingerModeRequest()) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.GetRingerModeResponse.decode(data) + // Reflection to decode enums to their string values, otherwise + // we only get an integer + var ringerMode = apk.builder.lookup('jp.co.cyberagent.stf.proto.RingerMode') + .children[response.mode].name + if (response.success) { + return ringerMode + } + throw new Error('Unable to get ringer mode') + }) + } + plugin.setWifiEnabled = function(enabled) { + return runServiceCommand(apk.wire.MessageType.SET_WIFI_ENABLED, new apk.wire.SetWifiEnabledRequest(enabled)) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.SetWifiEnabledResponse.decode(data) + if (!response.success) { + throw new Error('Unable to set Wifi') + } + }) + } + plugin.getWifiStatus = function() { + return runServiceCommand(apk.wire.MessageType.GET_WIFI_STATUS, new apk.wire.GetWifiStatusRequest()) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.GetWifiStatusResponse.decode(data) + if (response.success) { + return response.status + } + throw new Error('Unable to get Wifi status') + }) + } + plugin.sendCommand = function(command) { + log.info('Executing shell command ' + command + ' on ' + options.serial) + devutil.executeShellCommand(adb, options.serial, command) + } + plugin.setBluetoothEnabled = function(enabled) { + return runServiceCommand(apk.wire.MessageType.SET_BLUETOOTH_ENABLED, new apk.wire.SetBluetoothEnabledRequest(enabled)) + .timeout(10000) + .then(function(data) { + var response = apk.wire.SetBluetoothEnabledResponse.decode(data) + if (!response.success) { + throw new Error('Unable to set Bluetooth') + } + }) + } + plugin.cleanBluetoothBonds = function() { + return runServiceCommand(apk.wire.MessageType.DO_CLEAN_BLUETOOTH_BONDED_DEVICES, new apk.wire.DoCleanBluetoothBondedDevicesRequest()) + .timeout(10000) + .then(function(data) { + var response = apk.wire.DoCleanBluetoothBondedDevicesResponse.decode(data) + if (!response.success) { + throw new Error('Unable to clean Bluetooth bonded devices') + } + }) + } + plugin.getBluetoothStatus = function() { + return runServiceCommand(apk.wire.MessageType.GET_BLUETOOTH_STATUS, new apk.wire.GetBluetoothStatusRequest()) + .timeout(10000) + .then(function(data) { + var response = apk.wire.GetBluetoothStatusResponse.decode(data) + if (response.success) { + return response.status + } + throw new Error('Unable to get Bluetooth status') + }) + } + plugin.getSdStatus = function() { + return runServiceCommand(apk.wire.MessageType.GET_SD_STATUS, new apk.wire.GetSdStatusRequest()) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.GetSdStatusResponse.decode(data) + if (response.success) { + return response.mounted + } + throw new Error('Unable to get SD card status') + }) + } + plugin.pressKey = function(key) { + keyEvent({event: apk.wire.KeyEvent.PRESS, keyCode: keyutil.namedKey(key)}) + return Promise.resolve(true) + } + plugin.setMasterMute = function(mode) { + return runServiceCommand(apk.wire.MessageType.SET_MASTER_MUTE, new apk.wire.SetMasterMuteRequest(mode)) + .timeout(apiutil.GRPC_TIMEOUT) + .then(function(data) { + var response = apk.wire.SetMasterMuteResponse.decode(data) + if (!response.success) { + throw new Error('Unable to set master mute') + } + }) + } + return openAgent() + .then(openService) + .then(function() { + router + .on(wire.PhysicalIdentifyMessage, function(channel) { + var reply = wireutil.reply(options.serial) + plugin.identity() + push.send([ + channel + , reply.okay() + ]) + }) + .on(wire.KeyDownMessage, function(channel, message) { + try { + keyEvent({ + event: apk.wire.KeyEvent.DOWN + , keyCode: keyutil.namedKey(message.key) + }) + } + catch (e) { + log.warn(e.message) + } + }) + .on(wire.KeyUpMessage, function(channel, message) { + try { + keyEvent({ + event: apk.wire.KeyEvent.UP + , keyCode: keyutil.namedKey(message.key) + }) + } + catch (e) { + log.warn(e.message) + } + }) + .on(wire.KeyPressMessage, function(channel, message) { + try { + keyEvent({ + event: apk.wire.KeyEvent.PRESS + , keyCode: keyutil.namedKey(message.key) + }) + } + catch (e) { + log.warn(e.message) + } + }) + .on(wire.TypeMessage, function(channel, message) { + plugin.type(message.text) + }) + .on(wire.RotateMessage, function(channel, message) { + plugin.rotate(message.rotation) + }) + }) + .return(plugin) }) - .return(plugin) -}) diff --git a/lib/units/device/plugins/shell.js b/lib/units/device/plugins/shell.js index 34206c4386..2cf45a8386 100644 --- a/lib/units/device/plugins/shell.js +++ b/lib/units/device/plugins/shell.js @@ -13,62 +13,62 @@ export default syrup.serial() .dependency(push) .dependency(sub) .define(function(options, adb, router, push, sub) { - var log = logger.createLogger('device:plugins:shell') - router.on(wire.ShellCommandMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - log.info('Running shell command "%s"', message.command) - adb.getDevice(options.serial).shell(message.command) - .then(function(stream) { - var resolver = Promise.defer() - var timer - function forceStop() { - stream.end() - } - function keepAliveListener(channel, message) { - clearTimeout(timer) - timer = setTimeout(forceStop, message.timeout) - } - function readableListener() { - let chunk - while ((chunk = stream.read())) { + var log = logger.createLogger('device:plugins:shell') + router.on(wire.ShellCommandMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + log.info('Running shell command "%s"', message.command) + adb.getDevice(options.serial).shell(message.command) + .then(function(stream) { + var resolver = Promise.defer() + var timer + function forceStop() { + stream.end() + } + function keepAliveListener(channel, message) { + clearTimeout(timer) + timer = setTimeout(forceStop, message.timeout) + } + function readableListener() { + let chunk + while ((chunk = stream.read())) { + push.send([ + channel + , reply.progress(chunk) + ]) + } + } + function endListener() { + push.send([ + channel + , reply.okay(null) + ]) + resolver.resolve() + } + function errorListener(err) { + resolver.reject(err) + } + stream.setEncoding('utf8') + stream.on('readable', readableListener) + stream.on('end', endListener) + stream.on('error', errorListener) + sub.subscribe(channel) + router.on(wire.ShellKeepAliveMessage, keepAliveListener) + timer = setTimeout(forceStop, message.timeout) + return resolver.promise.finally(function() { + stream.removeListener('readable', readableListener) + stream.removeListener('end', endListener) + stream.removeListener('error', errorListener) + sub.unsubscribe(channel) + router.removeListener(wire.ShellKeepAliveMessage, keepAliveListener) + clearTimeout(timer) + }) + }) + .error(function(err) { + log.error('Shell command "%s" failed', message.command, err.stack) push.send([ channel - , reply.progress(chunk) + , reply.fail(err.message) ]) - } - } - function endListener() { - push.send([ - channel - , reply.okay(null) - ]) - resolver.resolve() - } - function errorListener(err) { - resolver.reject(err) - } - stream.setEncoding('utf8') - stream.on('readable', readableListener) - stream.on('end', endListener) - stream.on('error', errorListener) - sub.subscribe(channel) - router.on(wire.ShellKeepAliveMessage, keepAliveListener) - timer = setTimeout(forceStop, message.timeout) - return resolver.promise.finally(function() { - stream.removeListener('readable', readableListener) - stream.removeListener('end', endListener) - stream.removeListener('error', errorListener) - sub.unsubscribe(channel) - router.removeListener(wire.ShellKeepAliveMessage, keepAliveListener) - clearTimeout(timer) - }) - }) - .error(function(err) { - log.error('Shell command "%s" failed', message.command, err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) + }) }) }) -}) diff --git a/lib/units/device/plugins/solo.js b/lib/units/device/plugins/solo.js index 8e84451bb7..77c5e14dd7 100644 --- a/lib/units/device/plugins/solo.js +++ b/lib/units/device/plugins/solo.js @@ -13,30 +13,30 @@ export default syrup.serial() .dependency(router) .dependency(identity) .define(function(options, sub, push, router, identity) { - var log = logger.createLogger('device:plugins:solo') - // The channel should keep the same value between restarts, so that - // having the client side up to date all the time is not horribly painful. - function makeChannelId() { - var hash = crypto.createHash('sha1') - hash.update(options.serial) - return hash.digest('base64') - } - var channel = makeChannelId() - log.info('Subscribing to permanent channel "%s"', channel) - sub.subscribe(channel) - router.on(wire.ProbeMessage, function() { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceIdentityMessage(options.serial, identity.platform, identity.manufacturer, identity.operator, identity.model, identity.version, identity.abi, identity.sdk, new wire.DeviceDisplayMessage(identity.display), new wire.DevicePhoneMessage(identity.phone), identity.product, identity.cpuPlatform, identity.openGLESVersion, identity.marketName, identity.macAddress, identity.ram)) - ]) - }) - return { - channel: channel - , poke: function() { + var log = logger.createLogger('device:plugins:solo') + // The channel should keep the same value between restarts, so that + // having the client side up to date all the time is not horribly painful. + function makeChannelId() { + var hash = crypto.createHash('sha1') + hash.update(options.serial) + return hash.digest('base64') + } + var channel = makeChannelId() + log.info('Subscribing to permanent channel "%s"', channel) + sub.subscribe(channel) + router.on(wire.ProbeMessage, function() { push.send([ wireutil.global - , wireutil.envelope(new wire.DeviceReadyMessage(options.serial, channel)) + , wireutil.envelope(new wire.DeviceIdentityMessage(options.serial, identity.platform, identity.manufacturer, identity.operator, identity.model, identity.version, identity.abi, identity.sdk, new wire.DeviceDisplayMessage(identity.display), new wire.DevicePhoneMessage(identity.phone), identity.product, identity.cpuPlatform, identity.openGLESVersion, identity.marketName, identity.macAddress, identity.ram)) ]) + }) + return { + channel: channel + , poke: function() { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceReadyMessage(options.serial, channel)) + ]) + } } - } -}) + }) diff --git a/lib/units/device/plugins/store.js b/lib/units/device/plugins/store.js index e4d800fc08..2d1e0e2732 100644 --- a/lib/units/device/plugins/store.js +++ b/lib/units/device/plugins/store.js @@ -10,30 +10,30 @@ export default syrup.serial() .dependency(push) .dependency(adb) .define(function(options, router, push, adb) { - var log = logger.createLogger('device:plugins:store') - router.on(wire.StoreOpenMessage, function(channel) { - log.info('Opening Play Store') - var reply = wireutil.reply(options.serial) - adb.getDevice(options.serial).startActivity({ - action: 'android.intent.action.MAIN' - , component: 'com.android.vending/.AssetBrowserActivity' // FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - // FLAG_ACTIVITY_BROUGHT_TO_FRONT - // FLAG_ACTIVITY_NEW_TASK + var log = logger.createLogger('device:plugins:store') + router.on(wire.StoreOpenMessage, function(channel) { + log.info('Opening Play Store') + var reply = wireutil.reply(options.serial) + adb.getDevice(options.serial).startActivity({ + action: 'android.intent.action.MAIN' + , component: 'com.android.vending/.AssetBrowserActivity' // FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + // FLAG_ACTIVITY_BROUGHT_TO_FRONT + // FLAG_ACTIVITY_NEW_TASK - , flags: 0x10600000 - }) - .then(function() { - push.send([ - channel - , reply.okay() - ]) - }) - .catch(function(err) { - log.error('Play Store could not be opened', err.stack) - push.send([ - channel - , reply.fail() - ]) + , flags: 0x10600000 + }) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(function(err) { + log.error('Play Store could not be opened', err.stack) + push.send([ + channel + , reply.fail() + ]) + }) }) }) -}) diff --git a/lib/units/device/plugins/touch/index.js b/lib/units/device/plugins/touch/index.js index fd25dc5591..6f6f7a15f3 100644 --- a/lib/units/device/plugins/touch/index.js +++ b/lib/units/device/plugins/touch/index.js @@ -22,78 +22,78 @@ export default syrup.serial() .dependency(minitouch) .dependency(flags) .define(function(options, adb, router, minitouch, flags) { - var log = logger.createLogger('device:plugins:touch') - function TouchConsumer(config) { - EventEmitter.call(this) - this.actionQueue = [] - this.runningState = TouchConsumer.STATE_STOPPED - this.desiredState = new StateQueue() - this.output = null - this.socket = null - this.banner = null - this.touchConfig = config - this.starter = Promise.resolve(true) - this.failCounter = new FailCounter(3, 10000) - this.failCounter.on('exceedLimit', this._failLimitExceeded.bind(this)) - this.failed = false - this.readableListener = this._readableListener.bind(this) - this.writeQueue = [] - } - util.inherits(TouchConsumer, EventEmitter) - TouchConsumer.STATE_STOPPED = 1 - TouchConsumer.STATE_STARTING = 2 - TouchConsumer.STATE_STARTED = 3 - TouchConsumer.STATE_STOPPING = 4 - TouchConsumer.prototype._queueWrite = function(writer) { - switch (this.runningState) { + var log = logger.createLogger('device:plugins:touch') + function TouchConsumer(config) { + EventEmitter.call(this) + this.actionQueue = [] + this.runningState = TouchConsumer.STATE_STOPPED + this.desiredState = new StateQueue() + this.output = null + this.socket = null + this.banner = null + this.touchConfig = config + this.starter = Promise.resolve(true) + this.failCounter = new FailCounter(3, 10000) + this.failCounter.on('exceedLimit', this._failLimitExceeded.bind(this)) + this.failed = false + this.readableListener = this._readableListener.bind(this) + this.writeQueue = [] + } + util.inherits(TouchConsumer, EventEmitter) + TouchConsumer.STATE_STOPPED = 1 + TouchConsumer.STATE_STARTING = 2 + TouchConsumer.STATE_STARTED = 3 + TouchConsumer.STATE_STOPPING = 4 + TouchConsumer.prototype._queueWrite = function(writer) { + switch (this.runningState) { case TouchConsumer.STATE_STARTED: writer.call(this) break default: this.writeQueue.push(writer) break + } + } + TouchConsumer.prototype.touchDown = function(point) { + this._queueWrite(function() { + return this._write(util.format('d %s %s %s %s\n', point.contact, Math.ceil(this.touchConfig.origin.x(point) * this.banner.maxX), Math.ceil(this.touchConfig.origin.y(point) * this.banner.maxY), Math.ceil((point.pressure || 0.5) * this.banner.maxPressure))) + }) } - } - TouchConsumer.prototype.touchDown = function(point) { - this._queueWrite(function() { - return this._write(util.format('d %s %s %s %s\n', point.contact, Math.ceil(this.touchConfig.origin.x(point) * this.banner.maxX), Math.ceil(this.touchConfig.origin.y(point) * this.banner.maxY), Math.ceil((point.pressure || 0.5) * this.banner.maxPressure))) - }) - } - TouchConsumer.prototype.touchMove = function(point) { - this._queueWrite(function() { - return this._write(util.format('m %s %s %s %s\n', point.contact, Math.ceil(this.touchConfig.origin.x(point) * this.banner.maxX), Math.ceil(this.touchConfig.origin.y(point) * this.banner.maxY), Math.ceil((point.pressure || 0.5) * this.banner.maxPressure))) - }) - } - TouchConsumer.prototype.touchUp = function(point) { - this._queueWrite(function() { - return this._write(util.format('u %s\n', point.contact)) - }) - } - TouchConsumer.prototype.touchCommit = function() { - this._queueWrite(function() { - return this._write('c\n') - }) - } - TouchConsumer.prototype.touchReset = function() { - this._queueWrite(function() { - return this._write('r\n') - }) - } - TouchConsumer.prototype.tap = function(point) { - this.touchDown(point) - this.touchCommit() - this.touchUp(point) - this.touchCommit() - } - TouchConsumer.prototype._ensureState = function() { - if (this.desiredState.empty()) { - return + TouchConsumer.prototype.touchMove = function(point) { + this._queueWrite(function() { + return this._write(util.format('m %s %s %s %s\n', point.contact, Math.ceil(this.touchConfig.origin.x(point) * this.banner.maxX), Math.ceil(this.touchConfig.origin.y(point) * this.banner.maxY), Math.ceil((point.pressure || 0.5) * this.banner.maxPressure))) + }) + } + TouchConsumer.prototype.touchUp = function(point) { + this._queueWrite(function() { + return this._write(util.format('u %s\n', point.contact)) + }) + } + TouchConsumer.prototype.touchCommit = function() { + this._queueWrite(function() { + return this._write('c\n') + }) + } + TouchConsumer.prototype.touchReset = function() { + this._queueWrite(function() { + return this._write('r\n') + }) } - if (this.failed) { - log.warn('Will not apply desired state due to too many failures') - return + TouchConsumer.prototype.tap = function(point) { + this.touchDown(point) + this.touchCommit() + this.touchUp(point) + this.touchCommit() } - switch (this.runningState) { + TouchConsumer.prototype._ensureState = function() { + if (this.desiredState.empty()) { + return + } + if (this.failed) { + log.warn('Will not apply desired state due to too many failures') + return + } + switch (this.runningState) { case TouchConsumer.STATE_STARTING: case TouchConsumer.STATE_STOPPING: // Just wait. @@ -103,41 +103,41 @@ export default syrup.serial() this.runningState = TouchConsumer.STATE_STARTING this.starter = this._startService().bind(this) .then(function(out) { - this.output = new RiskyStream(out) - .on('unexpectedEnd', this._outputEnded.bind(this)) - return this._readOutput(this.output.stream) - }) + this.output = new RiskyStream(out) + .on('unexpectedEnd', this._outputEnded.bind(this)) + return this._readOutput(this.output.stream) + }) .then(function() { - return this._connectService() - }) + return this._connectService() + }) .then(function(socket) { - this.socket = new RiskyStream(socket) - .on('unexpectedEnd', this._socketEnded.bind(this)) - return this._readBanner(this.socket.stream) - }) + this.socket = new RiskyStream(socket) + .on('unexpectedEnd', this._socketEnded.bind(this)) + return this._readBanner(this.socket.stream) + }) .then(function(banner) { - this.banner = banner - return this._readUnexpected(this.socket.stream) - }) + this.banner = banner + return this._readUnexpected(this.socket.stream) + }) .then(function() { - this._processWriteQueue() - }) + this._processWriteQueue() + }) .then(function() { - this.runningState = TouchConsumer.STATE_STARTED - this.emit('start') - }) + this.runningState = TouchConsumer.STATE_STARTED + this.emit('start') + }) .catch(Promise.CancellationError, function() { - return this._stop() - }) + return this._stop() + }) .catch(function(err) { - return this._stop().finally(function() { - this.failCounter.inc() - this.emit('error', err) + return this._stop().finally(function() { + this.failCounter.inc() + this.emit('error', err) + }) }) - }) .finally(function() { - this._ensureState() - }) + this._ensureState() + }) } else { setImmediate(this._ensureState.bind(this)) @@ -154,18 +154,18 @@ export default syrup.serial() setImmediate(this._ensureState.bind(this)) } break + } + } + TouchConsumer.prototype.start = function() { + this.desiredState.push(TouchConsumer.STATE_STARTED) + this._ensureState() + } + TouchConsumer.prototype.stop = function() { + this.desiredState.push(TouchConsumer.STATE_STOPPED) + this._ensureState() } - } - TouchConsumer.prototype.start = function() { - this.desiredState.push(TouchConsumer.STATE_STARTED) - this._ensureState() - } - TouchConsumer.prototype.stop = function() { - this.desiredState.push(TouchConsumer.STATE_STOPPED) - this._ensureState() - } - TouchConsumer.prototype.restart = function() { - switch (this.runningState) { + TouchConsumer.prototype.restart = function() { + switch (this.runningState) { case TouchConsumer.STATE_STARTED: case TouchConsumer.STATE_STARTING: this.starter.cancel() @@ -173,303 +173,303 @@ export default syrup.serial() this.desiredState.push(TouchConsumer.STATE_STARTED) this._ensureState() break - } - } - TouchConsumer.prototype._configChanged = function() { - this.restart() - } - TouchConsumer.prototype._socketEnded = function() { - this.failCounter.inc() - this.restart() - } - TouchConsumer.prototype._outputEnded = function() { - this.failCounter.inc() - this.restart() - } - TouchConsumer.prototype._failLimitExceeded = function(limit, time) { - this._stop() - this.failed = true - this.emit('error', new Error(util.format('Failed more than %d times in %dms', limit, time))) - } - TouchConsumer.prototype._startService = function() { - return minitouch.run() - .timeout(10000) - } - TouchConsumer.prototype._readOutput = function(out) { - out.pipe(split()).on('data', function(line) { - var trimmed = line.toString().trim() - if (trimmed === '') { - return - } - if (/ERROR/.test(line)) { - log.fatal('minitouch error: "%s"', line) - return lifecycle.fatal() } - log.info('minitouch says: "%s"', line) - }) - } - TouchConsumer.prototype._connectService = function() { - function tryConnect(times, delay) { - return adb.getDevice(options.serial).openLocal('localabstract:minitouch') - .then(function(out) { - return out - }) - .catch(function(err) { - if (/closed/.test(err.message) && times > 1) { - return Promise.delay(delay) - .then(function() { - return tryConnect(times - 1, delay * 2) - }) - } - return Promise.reject(err) - }) } - log.info('Connecting to minitouch service') - // SH-03G can be very slow to start sometimes. Make sure we try long - // enough. - return tryConnect(7, 100) - } - TouchConsumer.prototype._stop = function() { - return this._disconnectService(this.socket).bind(this) - .timeout(2000) - .then(function() { - return this._stopService(this.output).timeout(10000) - }) - .then(function() { - this.runningState = TouchConsumer.STATE_STOPPED - this.emit('stop') - }) - .catch(function(err) { - // In practice we _should_ never get here due to _stopService() - // being quite aggressive. But if we do, well... assume it - // stopped anyway for now. - this.runningState = TouchConsumer.STATE_STOPPED - this.emit('error', err) - this.emit('stop') - }) - .finally(function() { - this.output = null - this.socket = null - this.banner = null - }) - } - TouchConsumer.prototype._disconnectService = function(socket) { - log.info('Disconnecting from minitouch service') - if (!socket || socket.ended) { - return Promise.resolve(true) + TouchConsumer.prototype._configChanged = function() { + this.restart() } - socket.stream.removeListener('readable', this.readableListener) - var endListener - return new Promise(function(resolve) { - socket.on('end', endListener = function() { - resolve(true) - }) - socket.stream.resume() - socket.end() - }) - .finally(function() { - socket.removeListener('end', endListener) - }) - } - TouchConsumer.prototype._stopService = function(output) { - log.info('Stopping minitouch service') - if (!output || output.ended) { - return Promise.resolve(true) + TouchConsumer.prototype._socketEnded = function() { + this.failCounter.inc() + this.restart() } - var pid = this.banner ? this.banner.pid : -1 - function kill(signal) { - if (pid <= 0) { - return Promise.reject(new Error('Minitouch service pid is unknown')) - } - var signum = { - SIGTERM: -15 - , SIGKILL: -9 - }[signal] - log.info('Sending %s to minitouch', signal) - return Promise.all([ - output.waitForEnd() - , adb.getDevice(options.serial).shell(['kill', signum, pid]) - .then(adbkit.Adb.util.readAll) - .return(true) - ]) - .timeout(2000) + TouchConsumer.prototype._outputEnded = function() { + this.failCounter.inc() + this.restart() } - function kindKill() { - return kill('SIGTERM') + TouchConsumer.prototype._failLimitExceeded = function(limit, time) { + this._stop() + this.failed = true + this.emit('error', new Error(util.format('Failed more than %d times in %dms', limit, time))) } - function forceKill() { - return kill('SIGKILL') + TouchConsumer.prototype._startService = function() { + return minitouch.run() + .timeout(10000) } - function forceEnd() { - log.info('Ending minitouch I/O as a last resort') - output.end() - return Promise.resolve(true) - } - return kindKill() - .catch(Promise.TimeoutError, forceKill) - .catch(forceEnd) - } - TouchConsumer.prototype._readBanner = function(socket) { - log.info('Reading minitouch banner') - var parser = new Parser(socket) - var banner = { - pid: -1 // @todo - , version: 0 - , maxContacts: 0 - , maxX: 0 - , maxY: 0 - , maxPressure: 0 - } - function readVersion() { - return parser.readLine() - .then(function(chunk) { - var args = chunk.toString().split(/ /g) - switch (args[0]) { - case 'v': - banner.version = Number(args[1]) - break - default: - throw new Error(util.format('Unexpected output "%s", expecting version line', chunk)) + TouchConsumer.prototype._readOutput = function(out) { + out.pipe(split()).on('data', function(line) { + var trimmed = line.toString().trim() + if (trimmed === '') { + return } + if (/ERROR/.test(line)) { + log.fatal('minitouch error: "%s"', line) + return lifecycle.fatal() + } + log.info('minitouch says: "%s"', line) }) } - function readLimits() { - return parser.readLine() - .then(function(chunk) { - var args = chunk.toString().split(/ /g) - switch (args[0]) { - case '^': - banner.maxContacts = args[1] - banner.maxX = args[2] - banner.maxY = args[3] - banner.maxPressure = args[4] - break - default: - throw new Error(util.format('Unknown output "%s", expecting limits line', chunk)) - } + TouchConsumer.prototype._connectService = function() { + function tryConnect(times, delay) { + return adb.getDevice(options.serial).openLocal('localabstract:minitouch') + .then(function(out) { + return out + }) + .catch(function(err) { + if (/closed/.test(err.message) && times > 1) { + return Promise.delay(delay) + .then(function() { + return tryConnect(times - 1, delay * 2) + }) + } + return Promise.reject(err) + }) + } + log.info('Connecting to minitouch service') + // SH-03G can be very slow to start sometimes. Make sure we try long + // enough. + return tryConnect(7, 100) + } + TouchConsumer.prototype._stop = function() { + return this._disconnectService(this.socket).bind(this) + .timeout(2000) + .then(function() { + return this._stopService(this.output).timeout(10000) + }) + .then(function() { + this.runningState = TouchConsumer.STATE_STOPPED + this.emit('stop') + }) + .catch(function(err) { + // In practice we _should_ never get here due to _stopService() + // being quite aggressive. But if we do, well... assume it + // stopped anyway for now. + this.runningState = TouchConsumer.STATE_STOPPED + this.emit('error', err) + this.emit('stop') + }) + .finally(function() { + this.output = null + this.socket = null + this.banner = null + }) + } + TouchConsumer.prototype._disconnectService = function(socket) { + log.info('Disconnecting from minitouch service') + if (!socket || socket.ended) { + return Promise.resolve(true) + } + socket.stream.removeListener('readable', this.readableListener) + var endListener + return new Promise(function(resolve) { + socket.on('end', endListener = function() { + resolve(true) + }) + socket.stream.resume() + socket.end() }) + .finally(function() { + socket.removeListener('end', endListener) + }) } - function readPid() { - return parser.readLine() - .then(function(chunk) { - var args = chunk.toString().split(/ /g) - switch (args[0]) { - case '$': - banner.pid = Number(args[1]) - break - default: - throw new Error(util.format('Unexpected output "%s", expecting pid line', chunk)) + TouchConsumer.prototype._stopService = function(output) { + log.info('Stopping minitouch service') + if (!output || output.ended) { + return Promise.resolve(true) + } + var pid = this.banner ? this.banner.pid : -1 + function kill(signal) { + if (pid <= 0) { + return Promise.reject(new Error('Minitouch service pid is unknown')) } - }) + var signum = { + SIGTERM: -15 + , SIGKILL: -9 + }[signal] + log.info('Sending %s to minitouch', signal) + return Promise.all([ + output.waitForEnd() + , adb.getDevice(options.serial).shell(['kill', signum, pid]) + .then(adbkit.Adb.util.readAll) + .return(true) + ]) + .timeout(2000) + } + function kindKill() { + return kill('SIGTERM') + } + function forceKill() { + return kill('SIGKILL') + } + function forceEnd() { + log.info('Ending minitouch I/O as a last resort') + output.end() + return Promise.resolve(true) + } + return kindKill() + .catch(Promise.TimeoutError, forceKill) + .catch(forceEnd) + } + TouchConsumer.prototype._readBanner = function(socket) { + log.info('Reading minitouch banner') + var parser = new Parser(socket) + var banner = { + pid: -1 // @todo + , version: 0 + , maxContacts: 0 + , maxX: 0 + , maxY: 0 + , maxPressure: 0 + } + function readVersion() { + return parser.readLine() + .then(function(chunk) { + var args = chunk.toString().split(/ /g) + switch (args[0]) { + case 'v': + banner.version = Number(args[1]) + break + default: + throw new Error(util.format('Unexpected output "%s", expecting version line', chunk)) + } + }) + } + function readLimits() { + return parser.readLine() + .then(function(chunk) { + var args = chunk.toString().split(/ /g) + switch (args[0]) { + case '^': + banner.maxContacts = args[1] + banner.maxX = args[2] + banner.maxY = args[3] + banner.maxPressure = args[4] + break + default: + throw new Error(util.format('Unknown output "%s", expecting limits line', chunk)) + } + }) + } + function readPid() { + return parser.readLine() + .then(function(chunk) { + var args = chunk.toString().split(/ /g) + switch (args[0]) { + case '$': + banner.pid = Number(args[1]) + break + default: + throw new Error(util.format('Unexpected output "%s", expecting pid line', chunk)) + } + }) + } + return readVersion() + .then(readLimits) + .then(readPid) + .return(banner) + .timeout(2000) + } + TouchConsumer.prototype._readUnexpected = function(socket) { + socket.on('readable', this.readableListener) + // We may already have data pending. + this.readableListener() } - return readVersion() - .then(readLimits) - .then(readPid) - .return(banner) - .timeout(2000) - } - TouchConsumer.prototype._readUnexpected = function(socket) { - socket.on('readable', this.readableListener) - // We may already have data pending. - this.readableListener() - } - TouchConsumer.prototype._readableListener = function() { - var chunk - while ((chunk = this.socket.stream.read())) { - log.warn('Unexpected output from minitouch socket', chunk) + TouchConsumer.prototype._readableListener = function() { + var chunk + while ((chunk = this.socket.stream.read())) { + log.warn('Unexpected output from minitouch socket', chunk) + } + } + TouchConsumer.prototype._processWriteQueue = function() { + for (var i = 0, l = this.writeQueue.length; i < l; ++i) { + this.writeQueue[i].call(this) + } + this.writeQueue = [] } - } - TouchConsumer.prototype._processWriteQueue = function() { - for (var i = 0, l = this.writeQueue.length; i < l; ++i) { - this.writeQueue[i].call(this) + TouchConsumer.prototype._write = function(chunk) { + this.socket.stream.write(chunk) } - this.writeQueue = [] - } - TouchConsumer.prototype._write = function(chunk) { - this.socket.stream.write(chunk) - } - function startConsumer() { - var touchConsumer = new TouchConsumer({ + function startConsumer() { + var touchConsumer = new TouchConsumer({ // Usually the touch origin is the same as the display's origin, // but sometimes it might not be. - origin: (function(origin) { - log.info('Touch origin is %s', origin) - return { - 'top left': { - x: function(point) { - return point.x - } - , y: function(point) { - return point.y - } - } // So far the only device we've seen exhibiting this behavior - // is Yoga Tablet 8. + origin: (function(origin) { + log.info('Touch origin is %s', origin) + return { + 'top left': { + x: function(point) { + return point.x + } + , y: function(point) { + return point.y + } + } // So far the only device we've seen exhibiting this behavior + // is Yoga Tablet 8. - , 'bottom left': { - x: function(point) { - return 1 - point.y + , 'bottom left': { + x: function(point) { + return 1 - point.y + } + , y: function(point) { + return point.x + } } - , y: function(point) { - return point.x - } - } - }[origin] - })(flags.get('forceTouchOrigin', 'top left')) - }) - var startListener, errorListener - return new Promise(function(resolve, reject) { - touchConsumer.on('start', startListener = function() { - resolve(touchConsumer) - }) - touchConsumer.on('error', errorListener = reject) - touchConsumer.start() - }) - .finally(function() { - touchConsumer.removeListener('start', startListener) - touchConsumer.removeListener('error', errorListener) - }) - } - return startConsumer() - .then(function(touchConsumer) { - var queue = new SeqQueue(100, 4) - touchConsumer.on('error', function(err) { - log.fatal('Touch consumer had an error', err.stack) - lifecycle.fatal() - }) - router - .on(wire.GestureStartMessage, function(channel, message) { - queue.start(message.seq) - }) - .on(wire.GestureStopMessage, function(channel, message) { - queue.push(message.seq, function() { - queue.stop() - }) - }) - .on(wire.TouchDownMessage, function(channel, message) { - queue.push(message.seq, function() { - touchConsumer.touchDown(message) - }) - }) - .on(wire.TouchMoveMessage, function(channel, message) { - queue.push(message.seq, function() { - touchConsumer.touchMove(message) - }) - }) - .on(wire.TouchUpMessage, function(channel, message) { - queue.push(message.seq, function() { - touchConsumer.touchUp(message) + }[origin] + })(flags.get('forceTouchOrigin', 'top left')) }) - }) - .on(wire.TouchCommitMessage, function(channel, message) { - queue.push(message.seq, function() { - touchConsumer.touchCommit() + var startListener, errorListener + return new Promise(function(resolve, reject) { + touchConsumer.on('start', startListener = function() { + resolve(touchConsumer) + }) + touchConsumer.on('error', errorListener = reject) + touchConsumer.start() }) - }) - .on(wire.TouchResetMessage, function(channel, message) { - queue.push(message.seq, function() { - touchConsumer.touchReset() + .finally(function() { + touchConsumer.removeListener('start', startListener) + touchConsumer.removeListener('error', errorListener) + }) + } + return startConsumer() + .then(function(touchConsumer) { + var queue = new SeqQueue(100, 4) + touchConsumer.on('error', function(err) { + log.fatal('Touch consumer had an error', err.stack) + lifecycle.fatal() + }) + router + .on(wire.GestureStartMessage, function(channel, message) { + queue.start(message.seq) + }) + .on(wire.GestureStopMessage, function(channel, message) { + queue.push(message.seq, function() { + queue.stop() + }) + }) + .on(wire.TouchDownMessage, function(channel, message) { + queue.push(message.seq, function() { + touchConsumer.touchDown(message) + }) + }) + .on(wire.TouchMoveMessage, function(channel, message) { + queue.push(message.seq, function() { + touchConsumer.touchMove(message) + }) + }) + .on(wire.TouchUpMessage, function(channel, message) { + queue.push(message.seq, function() { + touchConsumer.touchUp(message) + }) + }) + .on(wire.TouchCommitMessage, function(channel, message) { + queue.push(message.seq, function() { + touchConsumer.touchCommit() + }) + }) + .on(wire.TouchResetMessage, function(channel, message) { + queue.push(message.seq, function() { + touchConsumer.touchReset() + }) + }) + return touchConsumer }) - }) - return touchConsumer }) -}) diff --git a/lib/units/device/plugins/util/data.js b/lib/units/device/plugins/util/data.js index 23dc193562..4f369642f6 100644 --- a/lib/units/device/plugins/util/data.js +++ b/lib/units/device/plugins/util/data.js @@ -5,13 +5,13 @@ import identity from './identity.js' export default syrup.serial() .dependency(identity) .define(function(options, identity) { - const log = logger.createLogger('device:plugins:data') - function find() { - let data = deviceData.find(identity) - if (!data) { - log.warn('Unable to find device data - ', identity.model) + const log = logger.createLogger('device:plugins:data') + function find() { + let data = deviceData.find(identity) + if (!data) { + log.warn('Unable to find device data - ', identity.model) + } + return data } - return data - } - return find() -}) + return find() + }) diff --git a/lib/units/device/plugins/util/display.js b/lib/units/device/plugins/util/display.js index 6c2dad70a0..33a86c21d7 100644 --- a/lib/units/device/plugins/util/display.js +++ b/lib/units/device/plugins/util/display.js @@ -13,51 +13,51 @@ export default syrup.serial() .dependency(service) .dependency(options) .define(function(options, adb, minicap, service, screenOptions) { - var log = logger.createLogger('device:plugins:display') - function Display(id, properties) { - this.id = id - this.properties = properties - } - util.inherits(Display, EventEmitter) - Display.prototype.updateRotation = function(newRotation) { - log.info('Rotation changed to %d', newRotation) - this.properties.rotation = newRotation - this.emit('rotationChange', newRotation) - } - function infoFromMinicap(id) { - return minicap.run(util.format('-d %d -i', id)) - .then(streamutil.readAll) - .then(function(out) { - var match - if ((match = /^ERROR: (.*)$/.exec(out))) { - throw new Error(match[1]) - } - try { - return JSON.parse(out) - } - catch (e) { - throw new Error(out.toString()) - } + var log = logger.createLogger('device:plugins:display') + function Display(id, properties) { + this.id = id + this.properties = properties + } + util.inherits(Display, EventEmitter) + Display.prototype.updateRotation = function(newRotation) { + log.info('Rotation changed to %d', newRotation) + this.properties.rotation = newRotation + this.emit('rotationChange', newRotation) + } + function infoFromMinicap(id) { + return minicap.run(util.format('-d %d -i', id)) + .then(streamutil.readAll) + .then(function(out) { + var match + if ((match = /^ERROR: (.*)$/.exec(out))) { + throw new Error(match[1]) + } + try { + return JSON.parse(out) + } + catch (e) { + throw new Error(out.toString()) + } + }) + } + function infoFromService(id) { + return service.getDisplay(id) + } + function readInfo(id) { + log.info('Reading display info') + return infoFromService(id) + .catch(function() { + return infoFromMinicap(id) + }) + .then(function(properties) { + properties.url = screenOptions.publicUrl + return new Display(id, properties) + }) + } + return readInfo(0).then(function(display) { + service.on('rotationChange', function(data) { + display.updateRotation(data.rotation) + }) + return display }) - } - function infoFromService(id) { - return service.getDisplay(id) - } - function readInfo(id) { - log.info('Reading display info') - return infoFromService(id) - .catch(function() { - return infoFromMinicap(id) - }) - .then(function(properties) { - properties.url = screenOptions.publicUrl - return new Display(id, properties) - }) - } - return readInfo(0).then(function(display) { - service.on('rotationChange', function(data) { - display.updateRotation(data.rotation) - }) - return display }) -}) diff --git a/lib/units/device/plugins/util/flags.js b/lib/units/device/plugins/util/flags.js index 8ed95ee56e..38167ca99f 100644 --- a/lib/units/device/plugins/util/flags.js +++ b/lib/units/device/plugins/util/flags.js @@ -3,14 +3,14 @@ import data from './data.js' export default syrup.serial() .dependency(data) .define(function(options, data) { - return { - has: function(flag) { - return data && data.flags && !!data.flags[flag] + return { + has: function(flag) { + return data && data.flags && !!data.flags[flag] + } + , get: function(flag, defaultValue) { + return data && data.flags && typeof data.flags[flag] !== 'undefined' ? + data.flags[flag] : + defaultValue + } } - , get: function(flag, defaultValue) { - return data && data.flags && typeof data.flags[flag] !== 'undefined' ? - data.flags[flag] : - defaultValue - } - } -}) + }) diff --git a/lib/units/device/plugins/util/identity.js b/lib/units/device/plugins/util/identity.js index e5192e51fd..502c6062fc 100644 --- a/lib/units/device/plugins/util/identity.js +++ b/lib/units/device/plugins/util/identity.js @@ -9,16 +9,16 @@ export default syrup.serial() .dependency(display) .dependency(phone) .define(function(options, properties, display, phone) { - var log = logger.createLogger('device:plugins:identity') - function solve() { - log.info('Solving identity') - let identity = devutil.makeIdentity(options.serial, properties) - identity.display = display.properties - identity.phone = phone - if (options.deviceName) { - identity.module = options.deviceName + var log = logger.createLogger('device:plugins:identity') + function solve() { + log.info('Solving identity') + let identity = devutil.makeIdentity(options.serial, properties) + identity.display = display.properties + identity.phone = phone + if (options.deviceName) { + identity.module = options.deviceName + } + return identity } - return identity - } - return solve() -}) + return solve() + }) diff --git a/lib/units/device/plugins/util/phone.js b/lib/units/device/plugins/util/phone.js index 2f91e1b8f5..c2c7d0967e 100644 --- a/lib/units/device/plugins/util/phone.js +++ b/lib/units/device/plugins/util/phone.js @@ -4,16 +4,16 @@ import service from '../service.js' export default syrup.serial() .dependency(service) .define(function(options, service) { - const log = logger.createLogger('device:plugins:phone') - function fetch() { - log.info('Fetching phone info') - return service.getProperties([ - 'imei' - , 'imsi' - , 'phoneNumber' - , 'iccid' - , 'network' - ]) - } - return fetch() -}) + const log = logger.createLogger('device:plugins:phone') + function fetch() { + log.info('Fetching phone info') + return service.getProperties([ + 'imei' + , 'imsi' + , 'phoneNumber' + , 'iccid' + , 'network' + ]) + } + return fetch() + }) diff --git a/lib/units/device/plugins/util/urlformat.js b/lib/units/device/plugins/util/urlformat.js index 414a813f8c..1817a5072e 100644 --- a/lib/units/device/plugins/util/urlformat.js +++ b/lib/units/device/plugins/util/urlformat.js @@ -7,25 +7,25 @@ export default syrup.serial() .dependency(identity) .dependency(data) .define(function(options, identity, data) { - function createSlug() { - var model = identity.model - var name = data ? data.name.id : '' - return (name === '' || model.toLowerCase() === name.toLowerCase()) ? - tr.slugify(model) : - tr.slugify(name + ' ' + model) - } - var defaults = { - publicIp: options.publicIp - , serial: options.serial - , model: identity.model - , name: data ? data.name.id : '' - , slug: createSlug() - } - return function(template, port) { - return _.template(template, { - imports: { - slugify: tr.slugify - } - })(_.defaults({publicPort: port}, defaults)) - } -}) + function createSlug() { + var model = identity.model + var name = data ? data.name.id : '' + return (name === '' || model.toLowerCase() === name.toLowerCase()) ? + tr.slugify(model) : + tr.slugify(name + ' ' + model) + } + var defaults = { + publicIp: options.publicIp + , serial: options.serial + , model: identity.model + , name: data ? data.name.id : '' + , slug: createSlug() + } + return function(template, port) { + return _.template(template, { + imports: { + slugify: tr.slugify + } + })(_.defaults({publicPort: port}, defaults)) + } + }) diff --git a/lib/units/device/plugins/vnc/index.js b/lib/units/device/plugins/vnc/index.js index 80ec41d757..5f78259a2b 100644 --- a/lib/units/device/plugins/vnc/index.js +++ b/lib/units/device/plugins/vnc/index.js @@ -27,215 +27,215 @@ export default syrup.serial() .dependency(group) .dependency(solo) .define(function(options, router, push, screenStream, touch, group, solo) { - var log = logger.createLogger('device:plugins:vnc') - function vncAuthHandler(data) { - log.info('VNC authentication attempt using "%s"', data.response.toString('hex')) - var resolver = Promise.defer() - function notify() { - group.get() - .then(function(currentGroup) { - push.send([ - solo.channel - , wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage(options.serial, data.response.toString('hex'), currentGroup.group)) - ]) - }) - .catch(grouputil.NoGroupError, function() { - push.send([ - solo.channel - , wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage(options.serial, data.response.toString('hex'))) - ]) - }) - } - function joinListener(newGroup, identifier) { - if (!data.response.equals(Buffer.from(identifier || '', 'hex'))) { - resolver.reject(new Error('Someone else took the device')) + var log = logger.createLogger('device:plugins:vnc') + function vncAuthHandler(data) { + log.info('VNC authentication attempt using "%s"', data.response.toString('hex')) + var resolver = Promise.defer() + function notify() { + group.get() + .then(function(currentGroup) { + push.send([ + solo.channel + , wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage(options.serial, data.response.toString('hex'), currentGroup.group)) + ]) + }) + .catch(grouputil.NoGroupError, function() { + push.send([ + solo.channel + , wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage(options.serial, data.response.toString('hex'))) + ]) + }) } - } - function autojoinListener(identifier, joined) { - if (data.response.equals(Buffer.from(identifier, 'hex'))) { - if (joined) { - resolver.resolve() + function joinListener(newGroup, identifier) { + if (!data.response.equals(Buffer.from(identifier || '', 'hex'))) { + resolver.reject(new Error('Someone else took the device')) } - else { - resolver.reject(new Error('Device is already in use')) + } + function autojoinListener(identifier, joined) { + if (data.response.equals(Buffer.from(identifier, 'hex'))) { + if (joined) { + resolver.resolve() + } + else { + resolver.reject(new Error('Device is already in use')) + } } } + group.on('join', joinListener) + group.on('autojoin', autojoinListener) + router.on(wire.VncAuthResponsesUpdatedMessage, notify) + notify() + return resolver.promise + .timeout(5000) + .finally(function() { + group.removeListener('join', joinListener) + group.removeListener('autojoin', autojoinListener) + router.removeListener(wire.VncAuthResponsesUpdatedMessage, notify) + }) } - group.on('join', joinListener) - group.on('autojoin', autojoinListener) - router.on(wire.VncAuthResponsesUpdatedMessage, notify) - notify() - return resolver.promise - .timeout(5000) - .finally(function() { - group.removeListener('join', joinListener) - group.removeListener('autojoin', autojoinListener) - router.removeListener(wire.VncAuthResponsesUpdatedMessage, notify) - }) - } - function createServer() { - log.info('Starting VNC server on port %d', options.vncPort) - var opts = { - name: options.serial - , width: options.vncInitialSize[0] - , height: options.vncInitialSize[1] - , security: [{ + function createServer() { + log.info('Starting VNC server on port %d', options.vncPort) + var opts = { + name: options.serial + , width: options.vncInitialSize[0] + , height: options.vncInitialSize[1] + , security: [{ type: VncConnection.SECURITY_VNC , challenge: Buffer.alloc(16).fill(0) , auth: vncAuthHandler }] - } - var vnc = new VncServer(net.createServer({ - allowHalfOpen: true - }), opts) - var listeningListener, errorListener - return new Promise(function(resolve, reject) { - listeningListener = function() { - return resolve(vnc) - } - errorListener = function(err) { - return reject(err) - } - vnc.on('listening', listeningListener) - vnc.on('error', errorListener) - vnc.listen(options.vncPort) - }) - .finally(function() { - vnc.removeListener('listening', listeningListener) - vnc.removeListener('error', errorListener) - }) - } - return createServer() - .then(function(vnc) { - vnc.on('connection', function(conn) { - log.info('New VNC connection from %s', conn.conn.remoteAddress) - var id = util.format('vnc-%s', uuid.v4()) - var connState = { - lastFrame: null - , lastFrameTime: null - , frameWidth: 0 - , frameHeight: 0 - , sentFrameTime: null - , updateRequests: 0 - , frameConfig: { - format: jpeg.FORMAT_RGB - } } - var pointerTranslator = new PointerTranslator() - pointerTranslator.on('touchdown', function(event) { - touch.touchDown(event) - }) - pointerTranslator.on('touchmove', function(event) { - touch.touchMove(event) - }) - pointerTranslator.on('touchup', function(event) { - touch.touchUp(event) - }) - pointerTranslator.on('touchcommit', function() { - touch.touchCommit() - }) - function maybeSendFrame() { - if (!connState.updateRequests) { - return - } - if (!connState.lastFrame) { - return + var vnc = new VncServer(net.createServer({ + allowHalfOpen: true + }), opts) + var listeningListener, errorListener + return new Promise(function(resolve, reject) { + listeningListener = function() { + return resolve(vnc) } - if (connState.lastFrameTime === connState.sentFrameTime) { - return + errorListener = function(err) { + return reject(err) } - var decoded = jpeg.decompressSync(connState.lastFrame, connState.frameConfig) - conn.writeFramebufferUpdate([{ - xPosition: 0 - , yPosition: 0 - , width: decoded.width - , height: decoded.height - , encodingType: VncConnection.ENCODING_RAW - , data: decoded.data - } - , { - xPosition: 0 - , yPosition: 0 - , width: decoded.width - , height: decoded.height - , encodingType: VncConnection.ENCODING_DESKTOPSIZE - } - ]) - connState.updateRequests = 0 - connState.sentFrameTime = connState.lastFrameTime - } - function vncStartListener(frameProducer) { - return new Promise(function(resolve) { - connState.frameWidth = frameProducer.banner.virtualWidth - connState.frameHeight = frameProducer.banner.virtualHeight - resolve() - }) - } - function vncFrameListener(frame) { - return new Promise(function(resolve) { - connState.lastFrame = frame - connState.lastFrameTime = Date.now() - maybeSendFrame() - resolve() - }) - } - function groupLeaveListener() { - conn.end() - } - conn.on('authenticated', function() { - screenStream.updateProjection(options.vncInitialSize[0], options.vncInitialSize[1]) - screenStream.broadcastSet.insert(id, { - onStart: vncStartListener - , onFrame: vncFrameListener - }) - }) - conn.on('fbupdaterequest', function() { - connState.updateRequests += 1 - maybeSendFrame() + vnc.on('listening', listeningListener) + vnc.on('error', errorListener) + vnc.listen(options.vncPort) }) - conn.on('formatchange', function(format) { - var same = os.endianness() === 'BE' === - Boolean(format.bigEndianFlag) - var formatOrder = (format.redShift > format.blueShift) === same - switch (format.bitsPerPixel) { - case 8: - connState.frameConfig = { - format: jpeg.FORMAT_GRAY + .finally(function() { + vnc.removeListener('listening', listeningListener) + vnc.removeListener('error', errorListener) + }) + } + return createServer() + .then(function(vnc) { + vnc.on('connection', function(conn) { + log.info('New VNC connection from %s', conn.conn.remoteAddress) + var id = util.format('vnc-%s', uuid.v4()) + var connState = { + lastFrame: null + , lastFrameTime: null + , frameWidth: 0 + , frameHeight: 0 + , sentFrameTime: null + , updateRequests: 0 + , frameConfig: { + format: jpeg.FORMAT_RGB } - break - case 24: - connState.frameConfig = { - format: formatOrder ? jpeg.FORMAT_BGR : jpeg.FORMAT_RGB + } + var pointerTranslator = new PointerTranslator() + pointerTranslator.on('touchdown', function(event) { + touch.touchDown(event) + }) + pointerTranslator.on('touchmove', function(event) { + touch.touchMove(event) + }) + pointerTranslator.on('touchup', function(event) { + touch.touchUp(event) + }) + pointerTranslator.on('touchcommit', function() { + touch.touchCommit() + }) + function maybeSendFrame() { + if (!connState.updateRequests) { + return } - break - case 32: - var f - if (formatOrder) { - f = format.blueShift === 0 ? jpeg.FORMAT_BGRX : jpeg.FORMAT_XBGR + if (!connState.lastFrame) { + return } - else { - f = format.redShift === 0 ? jpeg.FORMAT_RGBX : jpeg.FORMAT_XRGB + if (connState.lastFrameTime === connState.sentFrameTime) { + return } - connState.frameConfig = { - format: f + var decoded = jpeg.decompressSync(connState.lastFrame, connState.frameConfig) + conn.writeFramebufferUpdate([{ + xPosition: 0 + , yPosition: 0 + , width: decoded.width + , height: decoded.height + , encodingType: VncConnection.ENCODING_RAW + , data: decoded.data } - break - } - }) - conn.on('pointer', function(event) { - pointerTranslator.push(event) - }) - conn.on('close', function() { - screenStream.broadcastSet.remove(id) - group.removeListener('leave', groupLeaveListener) - }) - conn.on('userActivity', function() { - group.keepalive() + , { + xPosition: 0 + , yPosition: 0 + , width: decoded.width + , height: decoded.height + , encodingType: VncConnection.ENCODING_DESKTOPSIZE + } + ]) + connState.updateRequests = 0 + connState.sentFrameTime = connState.lastFrameTime + } + function vncStartListener(frameProducer) { + return new Promise(function(resolve) { + connState.frameWidth = frameProducer.banner.virtualWidth + connState.frameHeight = frameProducer.banner.virtualHeight + resolve() + }) + } + function vncFrameListener(frame) { + return new Promise(function(resolve) { + connState.lastFrame = frame + connState.lastFrameTime = Date.now() + maybeSendFrame() + resolve() + }) + } + function groupLeaveListener() { + conn.end() + } + conn.on('authenticated', function() { + screenStream.updateProjection(options.vncInitialSize[0], options.vncInitialSize[1]) + screenStream.broadcastSet.insert(id, { + onStart: vncStartListener + , onFrame: vncFrameListener + }) + }) + conn.on('fbupdaterequest', function() { + connState.updateRequests += 1 + maybeSendFrame() + }) + conn.on('formatchange', function(format) { + var same = os.endianness() === 'BE' === + Boolean(format.bigEndianFlag) + var formatOrder = (format.redShift > format.blueShift) === same + switch (format.bitsPerPixel) { + case 8: + connState.frameConfig = { + format: jpeg.FORMAT_GRAY + } + break + case 24: + connState.frameConfig = { + format: formatOrder ? jpeg.FORMAT_BGR : jpeg.FORMAT_RGB + } + break + case 32: + var f + if (formatOrder) { + f = format.blueShift === 0 ? jpeg.FORMAT_BGRX : jpeg.FORMAT_XBGR + } + else { + f = format.redShift === 0 ? jpeg.FORMAT_RGBX : jpeg.FORMAT_XRGB + } + connState.frameConfig = { + format: f + } + break + } + }) + conn.on('pointer', function(event) { + pointerTranslator.push(event) + }) + conn.on('close', function() { + screenStream.broadcastSet.remove(id) + group.removeListener('leave', groupLeaveListener) + }) + conn.on('userActivity', function() { + group.keepalive() + }) + group.on('leave', groupLeaveListener) + }) + lifecycle.observe(function() { + vnc.close() + }) }) - group.on('leave', groupLeaveListener) - }) - lifecycle.observe(function() { - vnc.close() - }) }) -}) diff --git a/lib/units/device/plugins/vnc/util/connection.js b/lib/units/device/plugins/vnc/util/connection.js index ca134ea315..d5337713d3 100644 --- a/lib/units/device/plugins/vnc/util/connection.js +++ b/lib/units/device/plugins/vnc/util/connection.js @@ -110,15 +110,15 @@ VncConnection.prototype.writeFramebufferUpdate = function(rectangles) { rchunk.writeInt32BE(rect.encodingType, 8) this._write(rchunk) switch (rect.encodingType) { - case VncConnection.ENCODING_RAW: - this._write(rect.data) - break - case VncConnection.ENCODING_DESKTOPSIZE: - this._serverWidth = rect.width - this._serverHeight = rect.height - break - default: - throw new Error(util.format('Unsupported encoding type', rect.encodingType)) + case VncConnection.ENCODING_RAW: + this._write(rect.data) + break + case VncConnection.ENCODING_DESKTOPSIZE: + this._serverWidth = rect.width + this._serverHeight = rect.height + break + default: + throw new Error(util.format('Unsupported encoding type', rect.encodingType)) } }, this) } @@ -138,15 +138,15 @@ VncConnection.prototype._closeListener = function() { VncConnection.prototype._writeServerVersion = function() { // Yes, we could just format the string instead. Didn't feel like it. switch (this._serverVersion) { - case VncConnection.V3_003: - this._write(Buffer.from('RFB 003.003\n')) - break - case VncConnection.V3_007: - this._write(Buffer.from('RFB 003.007\n')) - break - case VncConnection.V3_008: - this._write(Buffer.from('RFB 003.008\n')) - break + case VncConnection.V3_003: + this._write(Buffer.from('RFB 003.003\n')) + break + case VncConnection.V3_007: + this._write(Buffer.from('RFB 003.007\n')) + break + case VncConnection.V3_008: + this._write(Buffer.from('RFB 003.008\n')) + break } } VncConnection.prototype._writeSupportedSecurity = function() { @@ -160,18 +160,18 @@ VncConnection.prototype._writeSupportedSecurity = function() { VncConnection.prototype._writeSecurityResult = function(result, reason) { var chunk switch (result) { - case VncConnection.SECURITYRESULT_OK: - chunk = Buffer.alloc(4) - chunk.writeUInt32BE(result, 0) - this._write(chunk) - break - case VncConnection.SECURITYRESULT_FAIL: - chunk = Buffer.alloc(4 + 4 + reason.length) - chunk.writeUInt32BE(result, 0) - chunk.writeUInt32BE(reason.length, 4) - chunk.write(reason, 8, reason.length) - this._write(chunk) - break + case VncConnection.SECURITYRESULT_OK: + chunk = Buffer.alloc(4) + chunk.writeUInt32BE(result, 0) + this._write(chunk) + break + case VncConnection.SECURITYRESULT_FAIL: + chunk = Buffer.alloc(4 + 4 + reason.length) + chunk.writeUInt32BE(result, 0) + chunk.writeUInt32BE(reason.length, 4) + chunk.write(reason, 8, reason.length) + this._write(chunk) + break } } VncConnection.prototype._writeServerInit = function() { @@ -212,15 +212,15 @@ VncConnection.prototype._auth = function(type, data) { var security = this._serverSupportedSecurityByType[type] this._blockingOps.push(security.auth(data).bind(this) .then(function() { - this._changeState(VncConnection.STATE_NEED_CLIENT_INIT) - this._writeSecurityResult(VncConnection.SECURITYRESULT_OK) - this.emit('authenticated') - this._read() - }) + this._changeState(VncConnection.STATE_NEED_CLIENT_INIT) + this._writeSecurityResult(VncConnection.SECURITYRESULT_OK) + this.emit('authenticated') + this._read() + }) .catch(function() { - this._writeSecurityResult(VncConnection.SECURITYRESULT_FAIL, 'Authentication failure') - this.end() - })) + this._writeSecurityResult(VncConnection.SECURITYRESULT_FAIL, 'Authentication failure') + this.end() + })) } VncConnection.prototype._unguardedRead = function() { var chunk, lo, hi @@ -229,177 +229,177 @@ VncConnection.prototype._unguardedRead = function() { debug('state', StateReverse[this._state]) chunk = null switch (this._state) { - case VncConnection.STATE_NEED_CLIENT_VERSION: - if ((chunk = this._consume(12))) { - if ((this._clientVersion = this._parseVersion(chunk)) === null) { - this.end() - return - } - debug('client version', this._clientVersion) - this._writeSupportedSecurity() - this._changeState(VncConnection.STATE_NEED_CLIENT_SECURITY) - } - break - case VncConnection.STATE_NEED_CLIENT_SECURITY: - if ((chunk = this._consume(1))) { - if ((this._clientSecurity = this._parseSecurity(chunk)) === null) { - this._writeSecurityResult(VncConnection.SECURITYRESULT_FAIL, 'Unimplemented security type') - this.end() - return - } - debug('client security', this._clientSecurity) - if (!(this._clientSecurity in this._serverSupportedSecurityByType)) { - this._writeSecurityResult(VncConnection.SECURITYRESULT_FAIL, 'Unsupported security type') - this.end() - return - } - switch (this._clientSecurity) { - case VncConnection.SECURITY_NONE: - // TODO: investigate more elegant way of passing security challenge - // his._auth(VncConnection.SECURITY_NONE) - this._changeState(VncConnection.STATE_NEED_CLIENT_INIT) - this._writeSecurityResult(VncConnection.SECURITYRESULT_OK) - this.emit('authenticated') - return - case VncConnection.SECURITY_VNC: - this._writeVncAuthChallenge() - this._changeState(VncConnection.STATE_NEED_CLIENT_VNC_AUTH) - break - } - } - break - case VncConnection.STATE_NEED_CLIENT_VNC_AUTH: - if ((chunk = this._consume(16))) { - this._auth(VncConnection.SECURITY_VNC, { - response: chunk - }) + case VncConnection.STATE_NEED_CLIENT_VERSION: + if ((chunk = this._consume(12))) { + if ((this._clientVersion = this._parseVersion(chunk)) === null) { + this.end() return } - break - case VncConnection.STATE_NEED_CLIENT_INIT: - if ((chunk = this._consume(1))) { - this._clientShare = chunk[0] - debug('client shareFlag', this._clientShare) - this._writeServerInit() - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) - } - break - case VncConnection.STATE_NEED_CLIENT_MESSAGE: - if ((chunk = this._consume(1))) { - switch (chunk[0]) { - case VncConnection.CLIENT_MESSAGE_SETPIXELFORMAT: - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT) - break - case VncConnection.CLIENT_MESSAGE_SETENCODINGS: - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS) - break - case VncConnection.CLIENT_MESSAGE_FBUPDATEREQUEST: - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST) - break - case VncConnection.CLIENT_MESSAGE_KEYEVENT: - this.emit('userActivity') - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_KEYEVENT) - break - case VncConnection.CLIENT_MESSAGE_POINTEREVENT: - this.emit('userActivity') - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_POINTEREVENT) - break - case VncConnection.CLIENT_MESSAGE_CLIENTCUTTEXT: - this.emit('userActivity') - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT) - break - default: - this._error(new Error(util.format('Unsupported message type %d', chunk[0]))) - return - } - } - break - case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT: - if ((chunk = this._consume(19))) { - // [0b, 3b) padding - this._clientPixelFormat = new PixelFormat({ - bitsPerPixel: chunk[3] - , depth: chunk[4] - , bigEndianFlag: chunk[5] - , trueColorFlag: chunk[6] - , redMax: chunk.readUInt16BE(7, true) - , greenMax: chunk.readUInt16BE(9, true) - , blueMax: chunk.readUInt16BE(11, true) - , redShift: chunk[13] - , greenShift: chunk[14] - , blueShift: chunk[15] - }) - // [16b, 19b) padding - debug('client pixel format', this._clientPixelFormat) - this.emit('formatchange', this._clientPixelFormat) - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) - } - break - case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS: - if ((chunk = this._consume(3))) { - // [0b, 1b) padding - this._clientEncodingCount = chunk.readUInt16BE(1, true) - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE) - } - break - case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE: - lo = 0 - hi = 4 * this._clientEncodingCount - if ((chunk = this._consume(hi))) { - this._clientEncodings = [] - while (lo < hi) { - this._clientEncodings.push(chunk.readInt32BE(lo, true)) - lo += 4 - } - debug('client encodings', this._clientEncodings) - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) - } - break - case VncConnection.STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST: - if ((chunk = this._consume(9))) { - this.emit('fbupdaterequest', { - incremental: chunk[0] - , xPosition: chunk.readUInt16BE(1, true) - , yPosition: chunk.readUInt16BE(3, true) - , width: chunk.readUInt16BE(5, true) - , height: chunk.readUInt16BE(7, true) - }) - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + debug('client version', this._clientVersion) + this._writeSupportedSecurity() + this._changeState(VncConnection.STATE_NEED_CLIENT_SECURITY) + } + break + case VncConnection.STATE_NEED_CLIENT_SECURITY: + if ((chunk = this._consume(1))) { + if ((this._clientSecurity = this._parseSecurity(chunk)) === null) { + this._writeSecurityResult(VncConnection.SECURITYRESULT_FAIL, 'Unimplemented security type') + this.end() + return } - break - case VncConnection.STATE_NEED_CLIENT_MESSAGE_KEYEVENT: - if ((chunk = this._consume(7))) { - // downFlag = chunk[0] - // [1b, 3b) padding - // key = chunk.readUInt32BE(3, true) - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + debug('client security', this._clientSecurity) + if (!(this._clientSecurity in this._serverSupportedSecurityByType)) { + this._writeSecurityResult(VncConnection.SECURITYRESULT_FAIL, 'Unsupported security type') + this.end() + return } - break - case VncConnection.STATE_NEED_CLIENT_MESSAGE_POINTEREVENT: - if ((chunk = this._consume(5))) { - this.emit('pointer', { - buttonMask: chunk[0] - , xPosition: chunk.readUInt16BE(1, true) / this._serverWidth - , yPosition: chunk.readUInt16BE(3, true) / this._serverHeight - }) - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + switch (this._clientSecurity) { + case VncConnection.SECURITY_NONE: + // TODO: investigate more elegant way of passing security challenge + // his._auth(VncConnection.SECURITY_NONE) + this._changeState(VncConnection.STATE_NEED_CLIENT_INIT) + this._writeSecurityResult(VncConnection.SECURITYRESULT_OK) + this.emit('authenticated') + return + case VncConnection.SECURITY_VNC: + this._writeVncAuthChallenge() + this._changeState(VncConnection.STATE_NEED_CLIENT_VNC_AUTH) + break } - break - case VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT: - if ((chunk = this._consume(7))) { - // [0b, 3b) padding - this._clientCutTextLength = chunk.readUInt32BE(3) - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE) + } + break + case VncConnection.STATE_NEED_CLIENT_VNC_AUTH: + if ((chunk = this._consume(16))) { + this._auth(VncConnection.SECURITY_VNC, { + response: chunk + }) + return + } + break + case VncConnection.STATE_NEED_CLIENT_INIT: + if ((chunk = this._consume(1))) { + this._clientShare = chunk[0] + debug('client shareFlag', this._clientShare) + this._writeServerInit() + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE: + if ((chunk = this._consume(1))) { + switch (chunk[0]) { + case VncConnection.CLIENT_MESSAGE_SETPIXELFORMAT: + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT) + break + case VncConnection.CLIENT_MESSAGE_SETENCODINGS: + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS) + break + case VncConnection.CLIENT_MESSAGE_FBUPDATEREQUEST: + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST) + break + case VncConnection.CLIENT_MESSAGE_KEYEVENT: + this.emit('userActivity') + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_KEYEVENT) + break + case VncConnection.CLIENT_MESSAGE_POINTEREVENT: + this.emit('userActivity') + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_POINTEREVENT) + break + case VncConnection.CLIENT_MESSAGE_CLIENTCUTTEXT: + this.emit('userActivity') + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT) + break + default: + this._error(new Error(util.format('Unsupported message type %d', chunk[0]))) + return } - break - case VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE: - if ((chunk = this._consume(this._clientCutTextLength))) { - // value = chunk - this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT: + if ((chunk = this._consume(19))) { + // [0b, 3b) padding + this._clientPixelFormat = new PixelFormat({ + bitsPerPixel: chunk[3] + , depth: chunk[4] + , bigEndianFlag: chunk[5] + , trueColorFlag: chunk[6] + , redMax: chunk.readUInt16BE(7, true) + , greenMax: chunk.readUInt16BE(9, true) + , blueMax: chunk.readUInt16BE(11, true) + , redShift: chunk[13] + , greenShift: chunk[14] + , blueShift: chunk[15] + }) + // [16b, 19b) padding + debug('client pixel format', this._clientPixelFormat) + this.emit('formatchange', this._clientPixelFormat) + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS: + if ((chunk = this._consume(3))) { + // [0b, 1b) padding + this._clientEncodingCount = chunk.readUInt16BE(1, true) + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE: + lo = 0 + hi = 4 * this._clientEncodingCount + if ((chunk = this._consume(hi))) { + this._clientEncodings = [] + while (lo < hi) { + this._clientEncodings.push(chunk.readInt32BE(lo, true)) + lo += 4 } - break - default: - throw new Error(util.format('Impossible state %d', this._state)) + debug('client encodings', this._clientEncodings) + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST: + if ((chunk = this._consume(9))) { + this.emit('fbupdaterequest', { + incremental: chunk[0] + , xPosition: chunk.readUInt16BE(1, true) + , yPosition: chunk.readUInt16BE(3, true) + , width: chunk.readUInt16BE(5, true) + , height: chunk.readUInt16BE(7, true) + }) + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_KEYEVENT: + if ((chunk = this._consume(7))) { + // downFlag = chunk[0] + // [1b, 3b) padding + // key = chunk.readUInt32BE(3, true) + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_POINTEREVENT: + if ((chunk = this._consume(5))) { + this.emit('pointer', { + buttonMask: chunk[0] + , xPosition: chunk.readUInt16BE(1, true) / this._serverWidth + , yPosition: chunk.readUInt16BE(3, true) / this._serverHeight + }) + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT: + if ((chunk = this._consume(7))) { + // [0b, 3b) padding + this._clientCutTextLength = chunk.readUInt32BE(3) + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE) + } + break + case VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE: + if ((chunk = this._consume(this._clientCutTextLength))) { + // value = chunk + this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE) + } + break + default: + throw new Error(util.format('Impossible state %d', this._state)) } } while (chunk) } @@ -418,11 +418,11 @@ VncConnection.prototype._parseVersion = function(chunk) { } VncConnection.prototype._parseSecurity = function(chunk) { switch (chunk[0]) { - case VncConnection.SECURITY_NONE: - case VncConnection.SECURITY_VNC: - return chunk[0] - default: - return null + case VncConnection.SECURITY_NONE: + case VncConnection.SECURITY_VNC: + return chunk[0] + default: + return null } } VncConnection.prototype._changeState = function(state) { diff --git a/lib/units/device/plugins/wifi.js b/lib/units/device/plugins/wifi.js index ea21f331fe..457e2b18e9 100644 --- a/lib/units/device/plugins/wifi.js +++ b/lib/units/device/plugins/wifi.js @@ -10,43 +10,43 @@ export default syrup.serial() .dependency(router) .dependency(push) .define(function(options, service, router, push) { - var log = logger.createLogger('device:plugins:wifi') - router.on(wire.WifiSetEnabledMessage, function(channel, message) { - var reply = wireutil.reply(options.serial) - log.info('Setting Wifi "%s"', message.enabled) - service.setWifiEnabled(message.enabled) - .timeout(30000) - .then(function() { - push.send([ - channel - , reply.okay() - ]) + var log = logger.createLogger('device:plugins:wifi') + router.on(wire.WifiSetEnabledMessage, function(channel, message) { + var reply = wireutil.reply(options.serial) + log.info('Setting Wifi "%s"', message.enabled) + service.setWifiEnabled(message.enabled) + .timeout(30000) + .then(function() { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(function(err) { + log.error('Setting Wifi enabled failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) - .catch(function(err) { - log.error('Setting Wifi enabled failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) + router.on(wire.WifiGetStatusMessage, function(channel) { + var reply = wireutil.reply(options.serial) + log.info('Getting Wifi status') + service.getWifiStatus() + .timeout(30000) + .then(function(enabled) { + push.send([ + channel + , reply.okay(enabled ? 'wifi_enabled' : 'wifi_disabled') + ]) + }) + .catch(function(err) { + log.error('Getting Wifi status failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) }) - router.on(wire.WifiGetStatusMessage, function(channel) { - var reply = wireutil.reply(options.serial) - log.info('Getting Wifi status') - service.getWifiStatus() - .timeout(30000) - .then(function(enabled) { - push.send([ - channel - , reply.okay(enabled ? 'wifi_enabled' : 'wifi_disabled') - ]) - }) - .catch(function(err) { - log.error('Getting Wifi status failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) - }) - }) -}) diff --git a/lib/units/device/resources/minicap.js b/lib/units/device/resources/minicap.js index b04442420f..be97d24f44 100644 --- a/lib/units/device/resources/minicap.js +++ b/lib/units/device/resources/minicap.js @@ -18,122 +18,122 @@ export default syrup.serial() .dependency(abi) .dependency(sdk) .define(function(options, adb, properties, abi, sdk) { - let log = logger.createLogger('device:resources:minicap') - let resources = { - bin: new Resource({ - src: pathutil.requiredMatch(abi.all.map(function(supportedAbi) { - return pathutil.module(util.format('@devicefarmer/minicap-prebuilt/prebuilt/%s/bin/minicap%s', supportedAbi, abi.pie ? '' : '-nopie')) - })) - , dest: [ - '/data/local/tmp/minicap' - , '/data/data/com.android.shell/minicap' - ] - , comm: 'minicap' - , mode: 0o755 - }) - , lib: new Resource({ + let log = logger.createLogger('device:resources:minicap') + let resources = { + bin: new Resource({ + src: pathutil.requiredMatch(abi.all.map(function(supportedAbi) { + return pathutil.module(util.format('@devicefarmer/minicap-prebuilt/prebuilt/%s/bin/minicap%s', supportedAbi, abi.pie ? '' : '-nopie')) + })) + , dest: [ + '/data/local/tmp/minicap' + , '/data/data/com.android.shell/minicap' + ] + , comm: 'minicap' + , mode: 0o755 + }) + , lib: new Resource({ // @todo The lib ABI should match the bin ABI. Currently we don't // have an x86_64 version of the binary while the lib supports it. - src: pathutil.match(abi.all.reduce(function(all, supportedAbi) { - return all.concat([ - pathutil.module(util.format('@devicefarmer/minicap-prebuilt/prebuilt/%s/lib/android-%s/minicap.so', supportedAbi, sdk.previewLevel)) - , pathutil.module(util.format('@devicefarmer/minicap-prebuilt/prebuilt/%s/lib/android-%s/minicap.so', supportedAbi, sdk.level)) - ]) - }, [])) - , dest: [ - '/data/local/tmp/minicap.so' - , '/data/data/com.android.shell/minicap.so' - ] - , comm: 'minicap.so' // Not actually used for anything but log output - , mode: 0o755 - }) - , apk: new Resource({ - src: pathutil.match([pathutil.module('@devicefarmer/minicap-prebuilt/prebuilt/noarch/minicap.apk')]) - , dest: ['/data/local/tmp/minicap.apk'] - , comm: 'minicap.apk' - , mode: 0o755 - }) - } - function removeResource(res) { - return adb.getDevice(options.serial).shell(['rm', '-f', res.dest]) - .then(function(out) { - return streamutil.readAll(out) - }) - .return(res) - } - function pushResource(res) { - return adb.getDevice(options.serial).push(res.src, res.dest, res.mode) - .then(function(transfer) { - return new Promise(function(resolve, reject) { - transfer.on('error', reject) - transfer.on('end', resolve) + src: pathutil.match(abi.all.reduce(function(all, supportedAbi) { + return all.concat([ + pathutil.module(util.format('@devicefarmer/minicap-prebuilt/prebuilt/%s/lib/android-%s/minicap.so', supportedAbi, sdk.previewLevel)) + , pathutil.module(util.format('@devicefarmer/minicap-prebuilt/prebuilt/%s/lib/android-%s/minicap.so', supportedAbi, sdk.level)) + ]) + }, [])) + , dest: [ + '/data/local/tmp/minicap.so' + , '/data/data/com.android.shell/minicap.so' + ] + , comm: 'minicap.so' // Not actually used for anything but log output + , mode: 0o755 }) - }) - .return(res) - } - function installResource(res) { - log.info('Installing "%s" as "%s"', res.src, res.dest) - function checkExecutable(res) { - return adb.getDevice(options.serial).stat(res.dest) - .then(function(stats) { - return (stats.mode & fs.constants.S_IXUSR) === fs.constants.S_IXUSR + , apk: new Resource({ + src: pathutil.match([pathutil.module('@devicefarmer/minicap-prebuilt/prebuilt/noarch/minicap.apk')]) + , dest: ['/data/local/tmp/minicap.apk'] + , comm: 'minicap.apk' + , mode: 0o755 }) } - return removeResource(res) - .then(pushResource) - .then(function(res) { - return checkExecutable(res).then(function(ok) { - if (!ok) { - log.info('Pushed "%s" not executable, attempting fallback location', res.comm) - res.shift() - return installResource(res) - } - return res - }) - }) - .return(res) - } - function installAll() { - let resourcesToBeinstalled = [] - if (resources.lib.src !== undefined) { - resourcesToBeinstalled.push(installResource(resources.bin)) - resourcesToBeinstalled.push(installResource(resources.lib)) + function removeResource(res) { + return adb.getDevice(options.serial).shell(['rm', '-f', res.dest]) + .then(function(out) { + return streamutil.readAll(out) + }) + .return(res) } - if (resources.apk.src !== undefined) { - resourcesToBeinstalled.push(installResource(resources.apk)) + function pushResource(res) { + return adb.getDevice(options.serial).push(res.src, res.dest, res.mode) + .then(function(transfer) { + return new Promise(function(resolve, reject) { + transfer.on('error', reject) + transfer.on('end', resolve) + }) + }) + .return(res) } - return Promise.all(resourcesToBeinstalled) - } - function stop() { - return devutil.killProcsByComm(adb, options.serial, resources.bin.comm, resources.bin.dest) - .timeout(15000) - } - return stop() - .then(installAll) - .then(function() { - return { - bin: resources.bin.dest - , lib: resources.lib.dest - , apk: resources.apk.dest - , run: function(mode, cmd) { - let runCmd - if (sdk.level >= 23) { // Use webp - runCmd = util.format('CLASSPATH=%s app_process /system/bin io.devicefarmer.minicap.Main %s', resources.apk.dest, cmd) - } - else { // Use jpeg - if (mode === 'minicap-bin' && resources.lib.src !== undefined) { - runCmd = util.format('LD_LIBRARY_PATH=%s exec %s %s', path.dirname(resources.lib.dest), resources.bin.dest, cmd) - } - else if (mode === 'minicap-apk' && resources.apk.src !== undefined) { - runCmd = util.format('CLASSPATH=%s app_process /system/bin io.devicefarmer.minicap.Main %s', resources.apk.dest, cmd) - } - else { - log.error('Missing resources/unknown minicap grabber: %s', mode) - } - } - log.info(runCmd) - return adb.getDevice(options.serial).shell(runCmd) + function installResource(res) { + log.info('Installing "%s" as "%s"', res.src, res.dest) + function checkExecutable(res) { + return adb.getDevice(options.serial).stat(res.dest) + .then(function(stats) { + return (stats.mode & fs.constants.S_IXUSR) === fs.constants.S_IXUSR + }) } + return removeResource(res) + .then(pushResource) + .then(function(res) { + return checkExecutable(res).then(function(ok) { + if (!ok) { + log.info('Pushed "%s" not executable, attempting fallback location', res.comm) + res.shift() + return installResource(res) + } + return res + }) + }) + .return(res) } + function installAll() { + let resourcesToBeinstalled = [] + if (resources.lib.src !== undefined) { + resourcesToBeinstalled.push(installResource(resources.bin)) + resourcesToBeinstalled.push(installResource(resources.lib)) + } + if (resources.apk.src !== undefined) { + resourcesToBeinstalled.push(installResource(resources.apk)) + } + return Promise.all(resourcesToBeinstalled) + } + function stop() { + return devutil.killProcsByComm(adb, options.serial, resources.bin.comm, resources.bin.dest) + .timeout(15000) + } + return stop() + .then(installAll) + .then(function() { + return { + bin: resources.bin.dest + , lib: resources.lib.dest + , apk: resources.apk.dest + , run: function(mode, cmd) { + let runCmd + if (sdk.level >= 23) { // Use webp + runCmd = util.format('CLASSPATH=%s app_process /system/bin io.devicefarmer.minicap.Main %s', resources.apk.dest, cmd) + } + else { // Use jpeg + if (mode === 'minicap-bin' && resources.lib.src !== undefined) { + runCmd = util.format('LD_LIBRARY_PATH=%s exec %s %s', path.dirname(resources.lib.dest), resources.bin.dest, cmd) + } + else if (mode === 'minicap-apk' && resources.apk.src !== undefined) { + runCmd = util.format('CLASSPATH=%s app_process /system/bin io.devicefarmer.minicap.Main %s', resources.apk.dest, cmd) + } + else { + log.error('Missing resources/unknown minicap grabber: %s', mode) + } + } + log.info(runCmd) + return adb.getDevice(options.serial).shell(runCmd) + } + } + }) }) -}) diff --git a/lib/units/device/resources/minirev.js b/lib/units/device/resources/minirev.js index 50af1f7236..38bc02a8b6 100644 --- a/lib/units/device/resources/minirev.js +++ b/lib/units/device/resources/minirev.js @@ -15,73 +15,73 @@ export default syrup.serial() .dependency(properties) .dependency(abi) .define(function(options, adb, properties, abi) { - var log = logger.createLogger('device:resources:minirev') - var resources = { - bin: new Resource({ - src: pathutil.requiredMatch(abi.all.map(function(supportedAbi) { - return pathutil.vendor(util.format('minirev/%s/minirev%s', supportedAbi, abi.pie ? '' : '-nopie')) - })) - , dest: [ - '/data/local/tmp/minirev' - , '/data/data/com.android.shell/minirev' - ] - , comm: 'minirev' - , mode: 0o755 - }) - } - function removeResource(res) { - return adb.getDevice(options.serial).shell(['rm', '-f', res.dest]) - .then(function(out) { - return streamutil.readAll(out) - }) - .return(res) - } - function pushResource(res) { - return adb.getDevice(options.serial).push(res.src, res.dest, res.mode) - .then(function(transfer) { - return new Promise(function(resolve, reject) { - transfer.on('error', reject) - transfer.on('end', resolve) - }) - }) - .return(res) - } - function installResource(res) { - log.info('Installing "%s" as "%s"', res.src, res.dest) - function checkExecutable(res) { - return adb.getDevice(options.serial).stat(res.dest) - .then(function(stats) { - return (stats.mode & fs.constants.S_IXUSR) === fs.constants.S_IXUSR + var log = logger.createLogger('device:resources:minirev') + var resources = { + bin: new Resource({ + src: pathutil.requiredMatch(abi.all.map(function(supportedAbi) { + return pathutil.vendor(util.format('minirev/%s/minirev%s', supportedAbi, abi.pie ? '' : '-nopie')) + })) + , dest: [ + '/data/local/tmp/minirev' + , '/data/data/com.android.shell/minirev' + ] + , comm: 'minirev' + , mode: 0o755 }) } - return removeResource(res) - .then(pushResource) - .then(function(res) { - return checkExecutable(res).then(function(ok) { - if (!ok) { - log.info('Pushed "%s" not executable, attempting fallback location', res.comm) - res.shift() - return installResource(res) + function removeResource(res) { + return adb.getDevice(options.serial).shell(['rm', '-f', res.dest]) + .then(function(out) { + return streamutil.readAll(out) + }) + .return(res) + } + function pushResource(res) { + return adb.getDevice(options.serial).push(res.src, res.dest, res.mode) + .then(function(transfer) { + return new Promise(function(resolve, reject) { + transfer.on('error', reject) + transfer.on('end', resolve) + }) + }) + .return(res) + } + function installResource(res) { + log.info('Installing "%s" as "%s"', res.src, res.dest) + function checkExecutable(res) { + return adb.getDevice(options.serial).stat(res.dest) + .then(function(stats) { + return (stats.mode & fs.constants.S_IXUSR) === fs.constants.S_IXUSR + }) + } + return removeResource(res) + .then(pushResource) + .then(function(res) { + return checkExecutable(res).then(function(ok) { + if (!ok) { + log.info('Pushed "%s" not executable, attempting fallback location', res.comm) + res.shift() + return installResource(res) + } + return res + }) + }) + .return(res) + } + function installAll() { + return Promise.all([ + installResource(resources.bin) + ]) + } + function stop() { + return devutil.killProcsByComm(adb, options.serial, resources.bin.comm, resources.bin.dest) + .timeout(15000) + } + return stop() + .then(installAll) + .then(function() { + return { + bin: resources.bin.dest } - return res }) - }) - .return(res) - } - function installAll() { - return Promise.all([ - installResource(resources.bin) - ]) - } - function stop() { - return devutil.killProcsByComm(adb, options.serial, resources.bin.comm, resources.bin.dest) - .timeout(15000) - } - return stop() - .then(installAll) - .then(function() { - return { - bin: resources.bin.dest - } }) -}) diff --git a/lib/units/device/resources/minitouch.js b/lib/units/device/resources/minitouch.js index 21fb22c049..19de00f0d0 100644 --- a/lib/units/device/resources/minitouch.js +++ b/lib/units/device/resources/minitouch.js @@ -13,76 +13,76 @@ export default syrup.serial() .dependency(adb) .dependency(abi) .define(function(options, adb, abi) { - var log = logger.createLogger('device:resources:minitouch') - var resources = { - bin: new Resource({ - src: pathutil.requiredMatch(abi.all.map(function(supportedAbi) { - return pathutil.module(util.format('@devicefarmer/minitouch-prebuilt/prebuilt/%s/bin/minitouch%s', supportedAbi, abi.pie ? '' : '-nopie')) - })) - , dest: [ - '/data/local/tmp/minitouch' - , '/data/data/com.android.shell/minitouch' - ] - , comm: 'minitouch' - , mode: 0o755 - }) - } - function removeResource(res) { - return adb.getDevice(options.serial).shell(['rm', '-f', res.dest]) - .then(function(out) { - return streamutil.readAll(out) - }) - .return(res) - } - function pushResource(res) { - return adb.getDevice(options.serial).push(res.src, res.dest, res.mode) - .then(function(transfer) { - return new Promise(function(resolve, reject) { - transfer.on('error', reject) - transfer.on('end', resolve) - }) - }) - .return(res) - } - function installResource(res) { - log.info('Installing "%s" as "%s"', res.src, res.dest) - function checkExecutable(res) { - return adb.getDevice(options.serial).stat(res.dest) - .then(function(stats) { - return (stats.mode & fs.constants.S_IXUSR) === fs.constants.S_IXUSR + var log = logger.createLogger('device:resources:minitouch') + var resources = { + bin: new Resource({ + src: pathutil.requiredMatch(abi.all.map(function(supportedAbi) { + return pathutil.module(util.format('@devicefarmer/minitouch-prebuilt/prebuilt/%s/bin/minitouch%s', supportedAbi, abi.pie ? '' : '-nopie')) + })) + , dest: [ + '/data/local/tmp/minitouch' + , '/data/data/com.android.shell/minitouch' + ] + , comm: 'minitouch' + , mode: 0o755 }) } - return removeResource(res) - .then(pushResource) - .then(function(res) { - return checkExecutable(res).then(function(ok) { - if (!ok) { - log.info('Pushed "%s" not executable, attempting fallback location', res.comm) - res.shift() - return installResource(res) - } - return res - }) - }) - .return(res) - } - function installAll() { - return Promise.all([ - installResource(resources.bin) - ]) - } - function stop() { - return devutil.killProcsByComm(adb, options.serial, resources.bin.comm, resources.bin.dest) - .timeout(15000) - } - return stop() - .then(installAll) - .then(function() { - return { - bin: resources.bin.dest - , run: function(cmd) { - return adb.getDevice(options.serial).shell(util.format('exec %s%s', resources.bin.dest, cmd ? util.format(' %s', cmd) : '')) + function removeResource(res) { + return adb.getDevice(options.serial).shell(['rm', '-f', res.dest]) + .then(function(out) { + return streamutil.readAll(out) + }) + .return(res) + } + function pushResource(res) { + return adb.getDevice(options.serial).push(res.src, res.dest, res.mode) + .then(function(transfer) { + return new Promise(function(resolve, reject) { + transfer.on('error', reject) + transfer.on('end', resolve) + }) + }) + .return(res) + } + function installResource(res) { + log.info('Installing "%s" as "%s"', res.src, res.dest) + function checkExecutable(res) { + return adb.getDevice(options.serial).stat(res.dest) + .then(function(stats) { + return (stats.mode & fs.constants.S_IXUSR) === fs.constants.S_IXUSR + }) } + return removeResource(res) + .then(pushResource) + .then(function(res) { + return checkExecutable(res).then(function(ok) { + if (!ok) { + log.info('Pushed "%s" not executable, attempting fallback location', res.comm) + res.shift() + return installResource(res) + } + return res + }) + }) + .return(res) } + function installAll() { + return Promise.all([ + installResource(resources.bin) + ]) + } + function stop() { + return devutil.killProcsByComm(adb, options.serial, resources.bin.comm, resources.bin.dest) + .timeout(15000) + } + return stop() + .then(installAll) + .then(function() { + return { + bin: resources.bin.dest + , run: function(cmd) { + return adb.getDevice(options.serial).shell(util.format('exec %s%s', resources.bin.dest, cmd ? util.format(' %s', cmd) : '')) + } + } + }) }) -}) diff --git a/lib/units/device/resources/scrcpy.js b/lib/units/device/resources/scrcpy.js index 1636505060..750fc85b60 100644 --- a/lib/units/device/resources/scrcpy.js +++ b/lib/units/device/resources/scrcpy.js @@ -15,99 +15,99 @@ export default syrup.serial() .dependency(abi) .dependency(sdk) .define(function(options, adb, properties, abi, sdk) { - let log = logger.createLogger('device:resources:scrcpy') - class Scrcpy extends EventEmitter { - constructor(config) { - super() - this._config = Object.assign({ - deviceId: options.serial - , port: 8099 - , maxSize: 600 - , bitrate: 999999999 - , tunnelForward: true - , tunnelDelay: 3000 - , crop: '9999:9999:0:0' - , sendFrameMeta: false - }, config) - this.adbClient = adb - } + let log = logger.createLogger('device:resources:scrcpy') + class Scrcpy extends EventEmitter { + constructor(config) { + super() + this._config = Object.assign({ + deviceId: options.serial + , port: 8099 + , maxSize: 600 + , bitrate: 999999999 + , tunnelForward: true + , tunnelDelay: 3000 + , crop: '9999:9999:0:0' + , sendFrameMeta: false + }, config) + this.adbClient = adb + } - /** + /** * Will connect to the android device, send & run the server and return deviceName, width and height. * After that data will be offered as a 'data' event. */ - async start() { + async start() { // Transfer server... - await this.adbClient.getDevice(options.serial).push(path.join(__dirname, 'scrcpy-server.jar'), '/data/local/tmp/scrcpy-server.jar') - .then(transfer => new Promise(((resolve, reject) => { - transfer.on('progress', (stats) => { - console.log('[%s] Pushed %d bytes so far', options.serial, stats.bytesTransferred) - }) - transfer.on('end', () => { - console.log('[%s] Push complete', options.serial) - resolve() - }) - transfer.on('error', reject) - }))) - .catch(e => { - console.log('Impossible to transfer server file:', e) - throw e - }) - // Run server - await this.adbClient.getDevice(options.serial).shell('CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / ' + + await this.adbClient.getDevice(options.serial).push(path.join(__dirname, 'scrcpy-server.jar'), '/data/local/tmp/scrcpy-server.jar') + .then(transfer => new Promise(((resolve, reject) => { + transfer.on('progress', (stats) => { + console.log('[%s] Pushed %d bytes so far', options.serial, stats.bytesTransferred) + }) + transfer.on('end', () => { + console.log('[%s] Push complete', options.serial) + resolve() + }) + transfer.on('error', reject) + }))) + .catch(e => { + console.log('Impossible to transfer server file:', e) + throw e + }) + // Run server + await this.adbClient.getDevice(options.serial).shell('CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / ' + `com.genymobile.scrcpy.Server ${this._config.maxSize} ${this._config.bitrate} ${this._config.tunnelForward} ` + `${this._config.crop} false`) - .catch(e => { - console.log('Impossible to run server:', e) - throw e - }) - console.log('Started server') - await this.adbClient.getDevice(options.serial).forward(`tcp:${this._config.port}`, 'localabstract:scrcpy') - .catch(e => { - console.log(`Impossible to forward port ${this._config.port}:`, e) - throw e - }) - console.log('Forwarded port') - this.socket = new PromiseSocket(new net.Socket()) - // Wait 1 sec to forward to work - await Promise.delay(this._config.tunnelDelay) - console.log('Started working, subscribing to data') - console.log('raw') - this._startStreamRaw() - // Connect - await this.socket.connect(this._config.port, '127.0.0.1') - .catch(e => { - console.log(`Impossible to connect "127.0.0.1:${this._config.port}":`, e) - throw e - }) - console.log('Connected') - // First chunk is 69 bytes length -> 1 dummy byte, 64 bytes for deviceName, 2 bytes for width & 2 bytes for height - const firstChunk = await this.socket.read(69) - .catch(e => { - console.log('Impossible to read first chunk:', e) - throw e - }) - console.log('Got first chunk') - const name = firstChunk.slice(1, 65).toString('utf8') - const width = firstChunk.readUInt16BE(65) - const height = firstChunk.readUInt16BE(67) - console.log(`${name}: ${width} x ${height}`) - return {name, width, height} - } - stop() { - if (this.socket) { - this.socket.destroy() + .catch(e => { + console.log('Impossible to run server:', e) + throw e + }) + console.log('Started server') + await this.adbClient.getDevice(options.serial).forward(`tcp:${this._config.port}`, 'localabstract:scrcpy') + .catch(e => { + console.log(`Impossible to forward port ${this._config.port}:`, e) + throw e + }) + console.log('Forwarded port') + this.socket = new PromiseSocket(new net.Socket()) + // Wait 1 sec to forward to work + await Promise.delay(this._config.tunnelDelay) + console.log('Started working, subscribing to data') + console.log('raw') + this._startStreamRaw() + // Connect + await this.socket.connect(this._config.port, '127.0.0.1') + .catch(e => { + console.log(`Impossible to connect "127.0.0.1:${this._config.port}":`, e) + throw e + }) + console.log('Connected') + // First chunk is 69 bytes length -> 1 dummy byte, 64 bytes for deviceName, 2 bytes for width & 2 bytes for height + const firstChunk = await this.socket.read(69) + .catch(e => { + console.log('Impossible to read first chunk:', e) + throw e + }) + console.log('Got first chunk') + const name = firstChunk.slice(1, 65).toString('utf8') + const width = firstChunk.readUInt16BE(65) + const height = firstChunk.readUInt16BE(67) + console.log(`${name}: ${width} x ${height}`) + return {name, width, height} } - } - _startStreamRaw() { + stop() { + if (this.socket) { + this.socket.destroy() + } + } + _startStreamRaw() { // console.log(this.socket.stream.) - this.socket.stream.on('data', d => { - console.log(d) - this.emit('rawData', d) - }) + this.socket.stream.on('data', d => { + console.log(d) + this.emit('rawData', d) + }) + } + } + return { + Scrcpy } - } - return { - Scrcpy - } -}) + }) diff --git a/lib/units/device/resources/service.js b/lib/units/device/resources/service.js index 6c666025e9..ac97aa0b0c 100644 --- a/lib/units/device/resources/service.js +++ b/lib/units/device/resources/service.js @@ -10,83 +10,83 @@ import adb from '../support/adb.js' export default syrup.serial() .dependency(adb) .define(function(options, adb) { - let log = logger.createLogger('device:resources:service') - let builder = ProtoBuf.loadProtoFile(pathutil.vendor('STFService/wire.proto')) - let STFServiceResource = { - requiredVersion: '2.6.2' - , pkg: 'jp.co.cyberagent.stf' - , main: 'jp.co.cyberagent.stf.Agent' - , apk: pathutil.vendor('STFService/STFService.apk') - , wire: builder.build().jp.co.cyberagent.stf.proto - , builder: builder - , startIntent: { - action: 'jp.co.cyberagent.stf.ACTION_START' - , component: 'jp.co.cyberagent.stf/.Service' + let log = logger.createLogger('device:resources:service') + let builder = ProtoBuf.loadProtoFile(pathutil.vendor('STFService/wire.proto')) + let STFServiceResource = { + requiredVersion: '2.6.2' + , pkg: 'jp.co.cyberagent.stf' + , main: 'jp.co.cyberagent.stf.Agent' + , apk: pathutil.vendor('STFService/STFService.apk') + , wire: builder.build().jp.co.cyberagent.stf.proto + , builder: builder + , startIntent: { + action: 'jp.co.cyberagent.stf.ACTION_START' + , component: 'jp.co.cyberagent.stf/.Service' + } } - } - // am startservice -a jp.co.cyberagent.stf.ACTION_START jp.co.cyberagent.stf/.Service - function getPath() { - return adb.getDevice(options.serial).shell(['pm', 'path', STFServiceResource.pkg]) - .then(function(out) { - return streamutil.findLine(out, (/^package:/)) - .timeout(15000) - .then(function(line) { - return line.substr(8) - }) - }) - } - function install() { - log.info('Checking whether we need to install STFService') - return getPath() - .then(function(installedPath) { - log.info('Running version check') - return adb.getDevice(options.serial).shell(util.format("CLASSPATH='%s' exec app_process /system/bin '%s' --version 2>/dev/null", installedPath, STFServiceResource.main)) + // am startservice -a jp.co.cyberagent.stf.ACTION_START jp.co.cyberagent.stf/.Service + function getPath() { + return adb.getDevice(options.serial).shell(['pm', 'path', STFServiceResource.pkg]) .then(function(out) { - return streamutil.readAll(out) - .timeout(10000) - .then(function(buffer) { - let version = buffer.toString() - if (semver.satisfies(version, STFServiceResource.requiredVersion)) { - return installedPath + return streamutil.findLine(out, (/^package:/)) + .timeout(15000) + .then(function(line) { + return line.substr(8) + }) + }) + } + function install() { + log.info('Checking whether we need to install STFService') + return getPath() + .then(function(installedPath) { + log.info('Running version check') + return adb.getDevice(options.serial).shell(util.format("CLASSPATH='%s' exec app_process /system/bin '%s' --version 2>/dev/null", installedPath, STFServiceResource.main)) + .then(function(out) { + return streamutil.readAll(out) + .timeout(10000) + .then(function(buffer) { + let version = buffer.toString() + if (semver.satisfies(version, STFServiceResource.requiredVersion)) { + return installedPath + } + else { + throw new Error(util.format('Incompatible version %s', version)) + } + }) + }) + }) + .catch(function() { + log.info('Installing STFService') + try { + adb.getDevice(options.serial).install(STFServiceResource.apk) + .then(function() { + return getPath() + }) } - else { - throw new Error(util.format('Incompatible version %s', version)) + catch (e) { + log.error('INSTALLING ERROR' + e) } }) - }) - }) - .catch(function() { - log.info('Installing STFService') - try { - adb.getDevice(options.serial).install(STFServiceResource.apk) - .then(function() { - return getPath() + } + function setPermission(path) { + return adb.getDevice(options.serial).shell([ + 'pm', 'grant', STFServiceResource.pkg + , 'android.permission.BLUETOOTH_CONNECT' + , 'android.permission.SYSTEM_ALERT_WINDOW' + ]) + .then(adbkit.Adb.util.readAll) + .then(function(out) { + log.debug('output of granting permissions to STFService: ' + out.toString()) + return path }) - } - catch (e) { - log.error('INSTALLING ERROR' + e) - } - }) - } - function setPermission(path) { - return adb.getDevice(options.serial).shell([ - 'pm', 'grant', STFServiceResource.pkg - , 'android.permission.BLUETOOTH_CONNECT' - , 'android.permission.SYSTEM_ALERT_WINDOW' - ]) - .then(adbkit.Adb.util.readAll) - .then(function(out) { - log.debug('output of granting permissions to STFService: ' + out.toString()) - return path - }) - } - return install() - .then(setPermission) - .then(function(path) { - adb.getDevice(options.serial).shell('ime enable jp.co.cyberagent.stf/.ADBKeyBoardService') - adb.getDevice(options.serial).shell('ime set jp.co.cyberagent.stf/.ADBKeyBoardService') - log.info('STFService up to date') - STFServiceResource.path = path - return STFServiceResource + } + return install() + .then(setPermission) + .then(function(path) { + adb.getDevice(options.serial).shell('ime enable jp.co.cyberagent.stf/.ADBKeyBoardService') + adb.getDevice(options.serial).shell('ime set jp.co.cyberagent.stf/.ADBKeyBoardService') + log.info('STFService up to date') + STFServiceResource.path = path + return STFServiceResource + }) }) -}) diff --git a/lib/units/device/support/abi.js b/lib/units/device/support/abi.js index 46c790a436..7fed2ea9c0 100644 --- a/lib/units/device/support/abi.js +++ b/lib/units/device/support/abi.js @@ -6,34 +6,34 @@ export default syrup.serial() .dependency(properties) .dependency(sdk) .define(function(options, properties, sdk) { - var log = logger.createLogger('device:support:abi') - return (function() { - function split(list) { - return list ? list.split(',') : [] - } - var abi = { - primary: properties['ro.product.cpu.abi'] - , pie: sdk.level >= 16 - , all: [] - , b32: [] - , b64: [] - } - // Since Android 5.0 - if (properties['ro.product.cpu.abilist']) { - abi.all = split(properties['ro.product.cpu.abilist']) - abi.b64 = split(properties['ro.product.cpu.abilist64']) - abi.b32 = split(properties['ro.product.cpu.abilist32']) - } - // Up to Android 4.4 - else { - abi.all.push(abi.primary) - abi.b32.push(abi.primary) - if (properties['ro.product.cpu.abi2']) { - abi.all.push(properties['ro.product.cpu.abi2']) - abi.b32.push(properties['ro.product.cpu.abi2']) + var log = logger.createLogger('device:support:abi') + return (function() { + function split(list) { + return list ? list.split(',') : [] } - } - log.info('Supports ABIs %s', abi.all.join(', ')) - return abi - })() -}) + var abi = { + primary: properties['ro.product.cpu.abi'] + , pie: sdk.level >= 16 + , all: [] + , b32: [] + , b64: [] + } + // Since Android 5.0 + if (properties['ro.product.cpu.abilist']) { + abi.all = split(properties['ro.product.cpu.abilist']) + abi.b64 = split(properties['ro.product.cpu.abilist64']) + abi.b32 = split(properties['ro.product.cpu.abilist32']) + } + // Up to Android 4.4 + else { + abi.all.push(abi.primary) + abi.b32.push(abi.primary) + if (properties['ro.product.cpu.abi2']) { + abi.all.push(properties['ro.product.cpu.abi2']) + abi.b32.push(properties['ro.product.cpu.abi2']) + } + } + log.info('Supports ABIs %s', abi.all.join(', ')) + return abi + })() + }) diff --git a/lib/units/device/support/adb.js b/lib/units/device/support/adb.js index 1b688b50fb..04ff480f09 100644 --- a/lib/units/device/support/adb.js +++ b/lib/units/device/support/adb.js @@ -4,19 +4,19 @@ import logger from '../../../util/logger.js' import * as promiseutil from '../../../util/promiseutil.js' export default syrup.serial() .define(function(options) { - var log = logger.createLogger('device:support:adb') - var adb = adbkit.Adb.createClient({ - host: options.adbHost - , port: options.adbPort - }) - adb.Keycode = adbkit.KeyCodes - function ensureBootComplete() { - return promiseutil.periodicNotify(adb.getDevice(options.serial).waitBootComplete(), 1000) - .progressed(function() { - log.info('Waiting for boot to complete') + var log = logger.createLogger('device:support:adb') + var adb = adbkit.Adb.createClient({ + host: options.adbHost + , port: options.adbPort }) - .timeout(options.bootCompleteTimeout) - } - return ensureBootComplete() - .return(adb) -}) + adb.Keycode = adbkit.KeyCodes + function ensureBootComplete() { + return promiseutil.periodicNotify(adb.getDevice(options.serial).waitBootComplete(), 1000) + .progressed(function() { + log.info('Waiting for boot to complete') + }) + .timeout(options.bootCompleteTimeout) + } + return ensureBootComplete() + .return(adb) + }) diff --git a/lib/units/device/support/properties.js b/lib/units/device/support/properties.js index 5c6f8c8269..eda7cd52c9 100644 --- a/lib/units/device/support/properties.js +++ b/lib/units/device/support/properties.js @@ -5,63 +5,63 @@ import adb from './adb.js' export default syrup.serial() .dependency(adb) .define(function(options, adb) { - const log = logger.createLogger('device:support:properties') - function load() { - log.info('Loading properties') - return adb.getDevice(options.serial).getProperties() - .then((props) => { - let sdk = props['ro.build.version.sdk'] - let command - if (sdk >= 24) { - command = "ip addr show wlan0 | grep 'link/ether '| cut -d' ' -f6 | xargs echo mac_address:" - } - else { - command = 'cat /sys/class/net/wlan0/address | xargs echo mac_address:' - } - return adb.getDevice(options.serial).shell(command) - .then((out) => { - return streamutil.findLine(out, (/^mac_address:/)) - .timeout(15000) - .then(function(line) { - let splitedLine = line.split('mac_address: ') - if (splitedLine.length > 1) { - props.mac_address = splitedLine[1] + const log = logger.createLogger('device:support:properties') + function load() { + log.info('Loading properties') + return adb.getDevice(options.serial).getProperties() + .then((props) => { + let sdk = props['ro.build.version.sdk'] + let command + if (sdk >= 24) { + command = "ip addr show wlan0 | grep 'link/ether '| cut -d' ' -f6 | xargs echo mac_address:" } else { - props.mac_address = 'secured' + command = 'cat /sys/class/net/wlan0/address | xargs echo mac_address:' } - return props + return adb.getDevice(options.serial).shell(command) + .then((out) => { + return streamutil.findLine(out, (/^mac_address:/)) + .timeout(15000) + .then(function(line) { + let splitedLine = line.split('mac_address: ') + if (splitedLine.length > 1) { + props.mac_address = splitedLine[1] + } + else { + props.mac_address = 'secured' + } + return props + }) + }) + .catch((e) => { + log.error(e) + log.info('setting secured mac address because of error') + props.mac_address = 'secured' + return props + }) }) - }) - .catch((e) => { - log.error(e) - log.info('setting secured mac address because of error') - props.mac_address = 'secured' - return props - }) - }) - .then((props) => { - return adb.getDevice(options.serial).shell('cat /proc/meminfo | grep MemTotal') - .then(function(out) { - return streamutil.findLine(out, (/^MemTotal:/)) - .timeout(15000) - .then(function(line) { - let total = line.match(/\d+/) - if (total) { - props.ram = total[0] - } - else { - props.ram = -1 - } - return props + .then((props) => { + return adb.getDevice(options.serial).shell('cat /proc/meminfo | grep MemTotal') + .then(function(out) { + return streamutil.findLine(out, (/^MemTotal:/)) + .timeout(15000) + .then(function(line) { + let total = line.match(/\d+/) + if (total) { + props.ram = total[0] + } + else { + props.ram = -1 + } + return props + }) + }) + .catch((e) => { + log.error(e) + props.ram = -1 + return props + }) }) - }) - .catch((e) => { - log.error(e) - props.ram = -1 - return props - }) - }) - } - return load() -}) + } + return load() + }) diff --git a/lib/units/device/support/sdk.js b/lib/units/device/support/sdk.js index eb48fe21ed..20aabc5448 100644 --- a/lib/units/device/support/sdk.js +++ b/lib/units/device/support/sdk.js @@ -4,23 +4,23 @@ import properties from './properties.js' export default syrup.serial() .dependency(properties) .define(function(options, properties) { - var log = logger.createLogger('device:support:sdk') - return (function() { - var level = parseInt(properties['ro.build.version.sdk'], 10) - var previewDelta = parseInt(properties['ro.build.version.preview_sdk'], 10) || 0 - var previewLevel = level + previewDelta - var sdk = { - level: level - , previewDelta: previewDelta - , previewLevel: previewLevel - , release: properties['ro.build.version.release'] - } - if (sdk.previewDelta) { - log.info('Supports SDK %s (base %s, preview delta +%s)', sdk.previewLevel, sdk.level, sdk.previewDelta) - } - else { - log.info('Supports SDK %s', sdk.level) - } - return sdk - })() -}) + var log = logger.createLogger('device:support:sdk') + return (function() { + var level = parseInt(properties['ro.build.version.sdk'], 10) + var previewDelta = parseInt(properties['ro.build.version.preview_sdk'], 10) || 0 + var previewLevel = level + previewDelta + var sdk = { + level: level + , previewDelta: previewDelta + , previewLevel: previewLevel + , release: properties['ro.build.version.release'] + } + if (sdk.previewDelta) { + log.info('Supports SDK %s (base %s, preview delta +%s)', sdk.previewLevel, sdk.level, sdk.previewDelta) + } + else { + log.info('Supports SDK %s', sdk.level) + } + return sdk + })() + }) diff --git a/lib/units/device/support/storage.js b/lib/units/device/support/storage.js index 6b41a1470e..00a5093c76 100644 --- a/lib/units/device/support/storage.js +++ b/lib/units/device/support/storage.js @@ -6,39 +6,39 @@ import request from 'postman-request' import logger from '../../../util/logger.js' export default syrup.serial() .define(function(options) { - var log = logger.createLogger('device:support:storage') - var plugin = Object.create(null) - plugin.store = function(type, stream, meta) { - log.info('device support storage :', arguments) - var resolver = Promise.defer() - var args = { - url: url.resolve(options.storageUrl, util.format('s/upload/%s', type)) - } - log.info('device support storage args :', args) - var req = request.post(args, function(err, res, body) { - if (err) { - log.error('Upload to "%s" failed', args.url, err.stack) - resolver.reject(err) - } - else if (res.statusCode !== 201) { - log.error('Upload to "%s" failed: HTTP %d', args.url, res.statusCode) - resolver.reject(new Error(util.format('Upload to "%s" failed: HTTP %d', args.url, res.statusCode))) + var log = logger.createLogger('device:support:storage') + var plugin = Object.create(null) + plugin.store = function(type, stream, meta) { + log.info('device support storage :', arguments) + var resolver = Promise.defer() + var args = { + url: url.resolve(options.storageUrl, util.format('s/upload/%s', type)) } - else { - try { - var result = JSON.parse(body) - log.info('Uploaded to "%s"', result.resources.file.href) - resolver.resolve(result.resources.file) - } - catch (err) { - log.error('Invalid JSON in response', err.stack, body) + log.info('device support storage args :', args) + var req = request.post(args, function(err, res, body) { + if (err) { + log.error('Upload to "%s" failed', args.url, err.stack) resolver.reject(err) } - } - }) - req.form() - .append('file', stream, meta) - return resolver.promise - } - return plugin -}) + else if (res.statusCode !== 201) { + log.error('Upload to "%s" failed: HTTP %d', args.url, res.statusCode) + resolver.reject(new Error(util.format('Upload to "%s" failed: HTTP %d', args.url, res.statusCode))) + } + else { + try { + var result = JSON.parse(body) + log.info('Uploaded to "%s"', result.resources.file.href) + resolver.resolve(result.resources.file) + } + catch (err) { + log.error('Invalid JSON in response', err.stack, body) + resolver.reject(err) + } + } + }) + req.form() + .append('file', stream, meta) + return resolver.promise + } + return plugin + }) diff --git a/lib/units/groups-engine/index.js b/lib/units/groups-engine/index.js index f60de07d4c..4d2b0022b9 100644 --- a/lib/units/groups-engine/index.js +++ b/lib/units/groups-engine/index.js @@ -9,7 +9,7 @@ import groupsScheduler from './scheduler/index.js' import groupsWatcher from './watchers/groups.js' import devicesWatcher from './watchers/devices.js' import usersWatcher from './watchers/users.js' -import dbapi from '../../db/api.mjs' +import * as dbapi from '../../db/api.js' export default (function(options) { const log = logger.createLogger('groups-engine') const channelRouter = new events.EventEmitter() @@ -24,9 +24,9 @@ export default (function(options) { }) }) .catch(function(err) { - log.fatal('Unable to connect to push endpoint', err) - lifecycle.fatal() - }) + log.fatal('Unable to connect to push endpoint', err) + lifecycle.fatal() + }) // Input const sub = zmqutil.socket('sub') Promise.map(options.endpoints.sub, function(endpoint) { @@ -39,9 +39,9 @@ export default (function(options) { }) }) .catch(function(err) { - log.fatal('Unable to connect to sub endpoint', err) - lifecycle.fatal() - }) + log.fatal('Unable to connect to sub endpoint', err) + lifecycle.fatal() + }) const pushdev = zmqutil.socket('push') Promise.map(options.endpoints.pushdev, function(endpoint) { return srv.resolve(endpoint).then(function(records) { @@ -53,9 +53,9 @@ export default (function(options) { }) }) .catch(function(err) { - log.fatal('Unable to connect to pushdev endpoint', err) - lifecycle.fatal() - }) + log.fatal('Unable to connect to pushdev endpoint', err) + lifecycle.fatal() + }) const subdev = zmqutil.socket('sub') Promise.map(options.endpoints.subdev, function(endpoint) { return srv.resolve(endpoint).then(function(records) { @@ -67,9 +67,9 @@ export default (function(options) { }) }) .catch(function(err) { - log.fatal('Unable to connect to subdev endpoint', err) - lifecycle.fatal() - }); + log.fatal('Unable to connect to subdev endpoint', err) + lifecycle.fatal() + }); [wireutil.global].forEach(function(channel) { log.info('Subscribing to permanent channel "%s"', channel) sub.subscribe(channel) diff --git a/lib/units/groups-engine/scheduler/index.js b/lib/units/groups-engine/scheduler/index.js index 40f58e1737..b61c1551e1 100644 --- a/lib/units/groups-engine/scheduler/index.js +++ b/lib/units/groups-engine/scheduler/index.js @@ -1,8 +1,9 @@ +import * as Sentry from '@sentry/node' import Promise from 'bluebird' import logger from '../../../util/logger.js' -import apiutil from '../../../util/apiutil.js' -import * as db from '../../../db/index.mjs' -import dbapi from '../../../db/api.mjs' +import * as apiutil from '../../../util/apiutil.js' +import * as db from '../../../db/index.js' +import * as dbapi from '../../../db/api.js' export default (function() { const log = logger.createLogger('groups-scheduler') function updateOriginGroupLifetime(group) { @@ -14,9 +15,9 @@ export default (function() { return client.collection('groups').updateOne({id: group.id}, { $set: { dates: [{ - start: new Date(now) - , stop: new Date(now + (group.dates[0].stop - group.dates[0].start)) - }] + start: new Date(now) + , stop: new Date(now + (group.dates[0].stop - group.dates[0].start)) + }] } }) }) @@ -24,8 +25,8 @@ export default (function() { return false }) .finally(function() { - return dbapi.adminUnlockGroup(lock) - }) + return dbapi.adminUnlockGroup(lock) + }) } function deleteUserGroup(group) { const lock = {} @@ -45,8 +46,8 @@ export default (function() { } }) .finally(function() { - return dbapi.adminUnlockGroup(lock) - }) + return dbapi.adminUnlockGroup(lock) + }) } function updateGroupDates(group, incr, isActive) { const repetitions = group.repetitions - incr @@ -64,8 +65,8 @@ export default (function() { }) }) .then(function() { - return dbapi.updateUserGroupDuration(group.owner.email, group.duration, duration) - }) + return dbapi.updateUserGroupDuration(group.owner.email, group.duration, duration) + }) } function doBecomeUnactiveGroup(group) { const lock = {} @@ -85,8 +86,8 @@ export default (function() { } }) .finally(function() { - return dbapi.adminUnlockGroup(lock) - }) + return dbapi.adminUnlockGroup(lock) + }) } function doCleanElapsedGroupDates(group, incr) { const lock = {} @@ -94,8 +95,8 @@ export default (function() { return lockingSuccessed ? updateGroupDates(group, incr, false) : false }) .finally(function() { - return dbapi.adminUnlockGroup(lock) - }) + return dbapi.adminUnlockGroup(lock) + }) } function doBecomeActiveGroup(group, incr) { const lock = {} @@ -103,48 +104,53 @@ export default (function() { return lockingSuccessed ? updateGroupDates(group, incr, true) : false }) .finally(function() { - return dbapi.adminUnlockGroup(lock) - }) + return dbapi.adminUnlockGroup(lock) + }) } dbapi.unlockBookingObjects().then(function() { - setInterval(function() { - const now = Date.now() - dbapi.getReadyGroupsOrderByIndex('startTime').then(function(groups) { - Promise.each(groups, (function(group) { - if (apiutil.isOriginGroup(group.class)) { - if (now >= group.dates[0].stop.getTime()) { - return updateOriginGroupLifetime(group) + setInterval(() => { + Sentry.startSpan({name: 'groups-engine scheduler'}, () => { + const now = Date.now() + log.info(`groups-engine tick. now: ${now}`) + dbapi.getReadyGroupsOrderByIndex('startTime').then(function(groups) { + Promise.each(groups, (function(group) { + if (apiutil.isOriginGroup(group.class)) { + if (now >= group.dates[0].stop.getTime()) { + return updateOriginGroupLifetime(group) + } } - } - else if ((group.isActive || group.state === apiutil.WAITING) && + else if ((group.isActive || group.state === apiutil.WAITING) && now >= group.dates[0].stop.getTime()) { - if (group.dates.length === 1 || (group.class === apiutil.ONCE && group.devices.length === 0)) { - return deleteUserGroup(group) - } - else { - return doBecomeUnactiveGroup(group) - } - } - else if (!group.isActive) { - for (const i in group.dates) { - if (now >= group.dates[i].stop.getTime()) { - if (group.dates[i].stop === group.dates[group.dates.length - 1].stop) { - return deleteUserGroup(group) - } - } - else if (now < group.dates[i].start.getTime()) { - return i > 0 ? doCleanElapsedGroupDates(group, i) : false + if (group.dates.length === 1 || (group.class === apiutil.ONCE && group.devices.length === 0)) { + return deleteUserGroup(group) } else { - return doBecomeActiveGroup(group, i) + return doBecomeUnactiveGroup(group) } } - } - return false - })) - }) - .catch(function(err) { - log.error('An error occured during groups scheduling', err.stack) + else if (!group.isActive) { + for (const i in group.dates) { + if (now >= group.dates[i].stop.getTime()) { + if (group.dates[i].stop === group.dates[group.dates.length - 1].stop) { + return deleteUserGroup(group) + } + } + else if (now < group.dates[i].start.getTime()) { + return i > 0 ? doCleanElapsedGroupDates(group, i) : false + } + else { + return doBecomeActiveGroup(group, i) + } + } + } + return false + })) + }) + .catch(function(err) { + Sentry.captureException(err) + + log.error('An error occured during groups scheduling', err.stack) + }) }) }, 1000) }) diff --git a/lib/units/groups-engine/watchers/devices.js b/lib/units/groups-engine/watchers/devices.js index 47de1ff99b..9aa55a4f23 100644 --- a/lib/units/groups-engine/watchers/devices.js +++ b/lib/units/groups-engine/watchers/devices.js @@ -6,8 +6,8 @@ import logger from '../../../util/logger.js' import timeutil from '../../../util/timeutil.js' import wireutil from '../../../wire/util.js' import wire from '../../../wire/index.js' -import dbapi from '../../../db/api.mjs' -import * as db from '../../../db/index.mjs' +import * as dbapi from '../../../db/api.js' +import * as db from '../../../db/index.js' export default (function(push, pushdev, channelRouter) { const log = logger.createLogger('watcher-devices') function sendReleaseDeviceControl(serial, channel) { @@ -49,13 +49,13 @@ export default (function(push, pushdev, channelRouter) { }, 5000) messageListener = wirerouter() .on(wire.LeaveGroupMessage, function(channel, message) { - if (message.serial === device.serial && + if (message.serial === device.serial && message.owner.email === device.owner.email) { - clearTimeout(responseTimer) - channelRouter.removeListener(wireutil.global, messageListener) - sendDeviceGroupChangeWrapper() - } - }) + clearTimeout(responseTimer) + channelRouter.removeListener(wireutil.global, messageListener) + sendDeviceGroupChangeWrapper() + } + }) .handler() channelRouter.on(wireutil.global, messageListener) sendReleaseDeviceControl(device.serial, device.channel) diff --git a/lib/units/groups-engine/watchers/groups.js b/lib/units/groups-engine/watchers/groups.js index 1cbe667a85..65b5820f6f 100644 --- a/lib/units/groups-engine/watchers/groups.js +++ b/lib/units/groups-engine/watchers/groups.js @@ -7,11 +7,11 @@ import Promise from 'bluebird' import _ from 'lodash' import logger from '../../../util/logger.js' import timeutil from '../../../util/timeutil.js' -import apiutil from '../../../util/apiutil.js' +import * as apiutil from '../../../util/apiutil.js' import wireutil from '../../../wire/util.js' import wire from '../../../wire/index.js' -import dbapi from '../../../db/api.mjs' -import * as db from '../../../db/index.mjs' +import * as dbapi from '../../../db/api.js' +import * as db from '../../../db/index.js' export default (function(push, pushdev, channelRouter) { const log = logger.createLogger('watcher-groups') function sendReleaseDeviceControl(serial, channel) { @@ -102,13 +102,13 @@ export default (function(push, pushdev, channelRouter) { }, 5000) messageListener = wirerouter() .on(wire.LeaveGroupMessage, function(channel, message) { - if (message.serial === serial && + if (message.serial === serial && message.owner.email === email) { - clearTimeout(responseTimer) - channelRouter.removeListener(wireutil.global, messageListener) - resolve(serial) - } - }) + clearTimeout(responseTimer) + channelRouter.removeListener(wireutil.global, messageListener) + resolve(serial) + } + }) .handler() channelRouter.on(wireutil.global, messageListener) sendReleaseDeviceControl(serial, device.channel) @@ -120,8 +120,8 @@ export default (function(push, pushdev, channelRouter) { }) }) .then(function(devices) { - sendGroupUsersChange(group, [email], _.without(devices, false), isAddedUser, 'GroupUser(s)Updated') - }) + sendGroupUsersChange(group, [email], _.without(devices, false), isAddedUser, 'GroupUser(s)Updated') + }) }) } else { @@ -135,10 +135,10 @@ export default (function(push, pushdev, channelRouter) { else { return doUpdateDevicesCurrentGroupFromOrigin(devices) .then(function() { - if (group === null) { - sendGroupUsersChange(oldGroup, oldGroup.users, [], false, 'GroupDeletedLater') - } - }) + if (group === null) { + sendGroupUsersChange(oldGroup, oldGroup.users, [], false, 'GroupDeletedLater') + } + }) } } function treatGroupDeletion(group) { @@ -148,8 +148,8 @@ export default (function(push, pushdev, channelRouter) { return dbapi.updateDeviceOriginGroup(serial, rootGroup) }) .then(function() { - return sendGroupUsersChange(group, group.users, [], false, 'GroupDeletedLater') - }) + return sendGroupUsersChange(group, group.users, [], false, 'GroupDeletedLater') + }) }) } else { diff --git a/lib/units/groups-engine/watchers/users.js b/lib/units/groups-engine/watchers/users.js index 86c293ad82..a0e491c07a 100644 --- a/lib/units/groups-engine/watchers/users.js +++ b/lib/units/groups-engine/watchers/users.js @@ -3,7 +3,7 @@ import _ from 'lodash' import logger from '../../../util/logger.js' import wireutil from '../../../wire/util.js' import wire from '../../../wire/index.js' -import * as db from '../../../db/index.mjs' +import * as db from '../../../db/index.js' export default (function(pushdev) { const log = logger.createLogger('watcher-users') function sendUserChange(user, isAddedGroup, groups, action, targets) { diff --git a/lib/units/ios-device/index.js b/lib/units/ios-device/index.js index 92fc4e0cc2..1533973c02 100755 --- a/lib/units/ios-device/index.js +++ b/lib/units/ios-device/index.js @@ -22,39 +22,39 @@ export default (function(options) { return syrup.serial() .dependency(logger$0) .define(function(options) { - const log = logger.createLogger('ios-device') - log.info('Preparing device options: ', options) - return syrup.serial() - .dependency(heartbeat) - .dependency(solo) - .dependency(info) - .dependency(wda) - .dependency(push) - .dependency(sub) - .dependency(group) - .dependency(storage) - .dependency(devicelog) - .dependency(stream) - .dependency(install) - .dependency(reboot) - .dependency(clipboard) - .dependency(remotedebug) - .define(function(options, heartbeat, solo, info, wda) { - if (process.send) { - process.send('ready') - } - try { - wda.connect() - solo.poke() - } - catch (err) { - log.error('err :', err) - } + const log = logger.createLogger('ios-device') + log.info('Preparing device options: ', options) + return syrup.serial() + .dependency(heartbeat) + .dependency(solo) + .dependency(info) + .dependency(wda) + .dependency(push) + .dependency(sub) + .dependency(group) + .dependency(storage) + .dependency(devicelog) + .dependency(stream) + .dependency(install) + .dependency(reboot) + .dependency(clipboard) + .dependency(remotedebug) + .define(function(options, heartbeat, solo, info, wda) { + if (process.send) { + process.send('ready') + } + try { + wda.connect() + solo.poke() + } + catch (err) { + log.error('err :', err) + } + }) + .consume(options) }) - .consume(options) - }) .consume(options) .catch((err) => { - lifecycle.fatal(err) - }) + lifecycle.fatal(err) + }) }) diff --git a/lib/units/ios-device/plugins/clipboard.js b/lib/units/ios-device/plugins/clipboard.js index deb982f87f..a1098dc2db 100755 --- a/lib/units/ios-device/plugins/clipboard.js +++ b/lib/units/ios-device/plugins/clipboard.js @@ -9,20 +9,20 @@ export default syrup.serial() .dependency(push) .dependency(wdaClient) .define(function(options, router, push, wdaClient) { - router.on(wire.CopyMessage, function(channel) { - const reply = wireutil.reply(options.serial) - wdaClient.getClipBoard() - .then(clipboard => { - push.send([ - channel - , reply.okay(clipboard) - ]) - }) - .catch(err => { - push.send([ - channel - , reply.fail('') - ]) + router.on(wire.CopyMessage, function(channel) { + const reply = wireutil.reply(options.serial) + wdaClient.getClipBoard() + .then(clipboard => { + push.send([ + channel + , reply.okay(clipboard) + ]) + }) + .catch(err => { + push.send([ + channel + , reply.fail('') + ]) + }) }) }) -}) diff --git a/lib/units/ios-device/plugins/devicelog.js b/lib/units/ios-device/plugins/devicelog.js index fa305b258f..102dcbd7f0 100755 --- a/lib/units/ios-device/plugins/devicelog.js +++ b/lib/units/ios-device/plugins/devicelog.js @@ -3,7 +3,7 @@ import wire from '../../../wire/index.js' import wireutil from '../../../wire/util.js' import {spawn} from 'child_process' import Promise from 'bluebird' -import dbapi from '../../../db/api.mjs' +import * as dbapi from '../../../db/api.js' import logger from '../../../util/logger.js' import push from '../../base-device/support/push.js' import router from '../../base-device/support/router.js' @@ -16,80 +16,80 @@ export default syrup .dependency(sub) .dependency(group) .define(function(options, push, router, group) { - const log = logger.createLogger('device:plugins:devicelog') - let launchArgs = [ - '--id' - , options.serial - , '--verbose' - , '--debug' - , '--noinstall' - , '--bundle' - ] - let DeviceLogger = { - appOptions: {} - , channel: '' - , stream: null - , setChannel: channel => { - this.channel = channel - } - , startLoggging: function(channel, deviceData) { - DeviceLogger.setChannel(channel) - if (deviceData && deviceData.bundleName.length !== 0 && + const log = logger.createLogger('device:plugins:devicelog') + let launchArgs = [ + '--id' + , options.serial + , '--verbose' + , '--debug' + , '--noinstall' + , '--bundle' + ] + let DeviceLogger = { + appOptions: {} + , channel: '' + , stream: null + , setChannel: channel => { + this.channel = channel + } + , startLoggging: function(channel, deviceData) { + DeviceLogger.setChannel(channel) + if (deviceData && deviceData.bundleName.length !== 0 && deviceData.bundleName.substr(-4) === '.app') { - group.get().then(group => { - launchArgs.push(deviceData.bundleName) - this.stream = spawn('ios-deploy1', launchArgs, { - shell: true - , cwd: deviceData.pathToApp - }) - this.stream.stdout.on('data', data => { - push.send([ - group.group - , wireutil.envelope(new wire.DeviceLogcatEntryMessage(options.serial, new Date().getTime() / 1000, this.stream.pid, this.stream.pid, 1, 'device:log:cat', data.toString())) - ]) + group.get().then(group => { + launchArgs.push(deviceData.bundleName) + this.stream = spawn('ios-deploy1', launchArgs, { + shell: true + , cwd: deviceData.pathToApp + }) + this.stream.stdout.on('data', data => { + push.send([ + group.group + , wireutil.envelope(new wire.DeviceLogcatEntryMessage(options.serial, new Date().getTime() / 1000, this.stream.pid, this.stream.pid, 1, 'device:log:cat', data.toString())) + ]) + }) + this.stream.on('close', err => { + log.fatal('Unable to get devicelog', err) + this.killLoggingProcess() + }) }) - this.stream.on('close', err => { - log.fatal('Unable to get devicelog', err) - this.killLoggingProcess() - }) - }) + } } - } - , killLoggingProcess: () => { - if (this.stream) { - process.kill(-this.stream.pid) - this.stream.kill() - this.stream = null - this.channel = '' + , killLoggingProcess: () => { + if (this.stream) { + process.kill(-this.stream.pid) + this.stream.kill() + this.stream = null + this.channel = '' + } } } - } - group.on('leave', DeviceLogger.killLoggingProcess) - router - .on(wire.LogcatStartMessage, function(channel, message) { - let reply = wireutil.reply(options.serial) - dbapi.loadDeviceBySerial(options.serial).then(device => { - if (device.installedApps) { - let data = device.installedApps.filter(item => { - return item.bundleName === message.filters[0].tag + group.on('leave', DeviceLogger.killLoggingProcess) + router + .on(wire.LogcatStartMessage, function(channel, message) { + let reply = wireutil.reply(options.serial) + dbapi.loadDeviceBySerial(options.serial).then(device => { + if (device.installedApps) { + let data = device.installedApps.filter(item => { + return item.bundleName === message.filters[0].tag + }) + DeviceLogger.startLoggging(channel, data[0]) + push.send([channel, reply.okay('success')]) + } + else { + log.error('No installed apps.') + } }) - DeviceLogger.startLoggging(channel, data[0]) - push.send([channel, reply.okay('success')]) - } - else { - log.error('No installed apps.') - } - }) - .catch(err => { - log.error(err) - DeviceLogger.killLoggingProcess() - }) - }) - .on(wire.LogcatStopMessage, function(channel, data) { - DeviceLogger.killLoggingProcess() - }) - .on(wire.GroupMessage, function(channel, data) { - DeviceLogger.channel = channel + .catch(err => { + log.error(err) + DeviceLogger.killLoggingProcess() + }) + }) + .on(wire.LogcatStopMessage, function(channel, data) { + DeviceLogger.killLoggingProcess() + }) + .on(wire.GroupMessage, function(channel, data) { + DeviceLogger.channel = channel + }) + return Promise.resolve() }) - return Promise.resolve() -}) diff --git a/lib/units/ios-device/plugins/devicenotifier.js b/lib/units/ios-device/plugins/devicenotifier.js index c1c3e2898d..410c8713ae 100644 --- a/lib/units/ios-device/plugins/devicenotifier.js +++ b/lib/units/ios-device/plugins/devicenotifier.js @@ -9,34 +9,34 @@ export default syrup.serial() .dependency(push) .dependency(group) .define(function(options, push, group) { - const log = logger.createLogger('device:plugins:notifier') - const notifier = {} - notifier.setDeviceTemporaryUnavailable = function(err) { - group.get() - .then((currentGroup) => { - push.send([ - currentGroup.group - , wireutil.envelope(new wire.TemporarilyUnavailableMessage(options.serial)) - ]) - }) - .catch(err => { - log.error('Cannot set device temporary unavailable', err) - }) - } - notifier.setDeviceAbsent = function(err) { - if (err.statusCode) { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceStatusMessage(options.serial, 1)) - ]) + const log = logger.createLogger('device:plugins:notifier') + const notifier = {} + notifier.setDeviceTemporaryUnavailable = function(err) { + group.get() + .then((currentGroup) => { + push.send([ + currentGroup.group + , wireutil.envelope(new wire.TemporarilyUnavailableMessage(options.serial)) + ]) + }) + .catch(err => { + log.error('Cannot set device temporary unavailable', err) + }) } - else { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceAbsentMessage(options.serial)) - ]) + notifier.setDeviceAbsent = function(err) { + if (err.statusCode) { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceStatusMessage(options.serial, 1)) + ]) + } + else { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceAbsentMessage(options.serial)) + ]) + } + lifecycle.graceful(err) } - lifecycle.graceful(err) - } - return notifier -}) + return notifier + }) diff --git a/lib/units/ios-device/plugins/group.js b/lib/units/ios-device/plugins/group.js index 5d9f7e3565..c5352e331d 100755 --- a/lib/units/ios-device/plugins/group.js +++ b/lib/units/ios-device/plugins/group.js @@ -6,10 +6,10 @@ import wire from '../../../wire/index.js' import wireutil from '../../../wire/util.js' import * as grouputil from '../../../util/grouputil.js' import lifecycle from '../../../util/lifecycle.js' -import dbapi from '../../../db/api.mjs' +import * as dbapi from '../../../db/api.js' import request from 'request-promise' import iosutil from './util/iosutil.js' -import apiutil from '../../../util/apiutil.js' +import * as apiutil from '../../../util/apiutil.js' import solo from './solo.js' import router from '../../base-device/support/router.js' import push from '../../base-device/support/push.js' @@ -22,234 +22,234 @@ export default syrup.serial() .dependency(sub) .dependency(channels) .define((options, solo, router, push, sub, channels) => { - const log = logger.createLogger('device:plugins:group') - const baseUrl = iosutil.getUri(options.wdaHost, options.wdaPort) - let currentGroup = null - let plugin = new events.EventEmitter() - plugin.get = Promise.method(() => { - if (!currentGroup) { - throw new grouputil.NoGroupError() - } - return currentGroup - }) - plugin.join = (newGroup, timeout, usage) => { - return plugin.get() - .then(() => { - if (currentGroup.group !== newGroup.group) { - throw new grouputil.AlreadyGroupedError() + const log = logger.createLogger('device:plugins:group') + const baseUrl = iosutil.getUri(options.wdaHost, options.wdaPort) + let currentGroup = null + let plugin = new events.EventEmitter() + plugin.get = Promise.method(() => { + if (!currentGroup) { + throw new grouputil.NoGroupError() } - log.info('Update timeout for ', apiutil.QUARTER_MINUTES) - channels.updateTimeout(currentGroup.group, apiutil.QUARTER_MINUTES) - let newTimeout = channels.getTimeout(currentGroup.group) - dbapi.enhanceStatusChangedAt(options.serial, newTimeout).then(() => { - return currentGroup - }) + return currentGroup }) - .catch(grouputil.NoGroupError, () => { - currentGroup = newGroup - log.important('Now owned by "%s"', currentGroup.email) - log.important('Device now in group "%s"', currentGroup.name) - log.info('Rent time is ' + timeout) - log.info('Subscribing to group channel "%s"', currentGroup.group) - channels.register(currentGroup.group, { - timeout: timeout || options.groupTimeout - , alias: solo.channel - }) - dbapi.enhanceStatusChangedAt(options.serial, timeout) - sub.subscribe(currentGroup.group) - push.send([ - wireutil.global - , wireutil.envelope(new wire.JoinGroupMessage(options.serial, currentGroup, usage)) - ]) - const handleRequest = (reqOptions) => { - return new Promise((resolve, reject) => { - request(reqOptions) - .then((res) => { - resolve(res) + plugin.join = (newGroup, timeout, usage) => { + return plugin.get() + .then(() => { + if (currentGroup.group !== newGroup.group) { + throw new grouputil.AlreadyGroupedError() + } + log.info('Update timeout for ', apiutil.QUARTER_MINUTES) + channels.updateTimeout(currentGroup.group, apiutil.QUARTER_MINUTES) + let newTimeout = channels.getTimeout(currentGroup.group) + dbapi.enhanceStatusChangedAt(options.serial, newTimeout).then(() => { + return currentGroup }) - .catch((err) => { - reject(err) + }) + .catch(grouputil.NoGroupError, () => { + currentGroup = newGroup + log.important('Now owned by "%s"', currentGroup.email) + log.important('Device now in group "%s"', currentGroup.name) + log.info('Rent time is ' + timeout) + log.info('Subscribing to group channel "%s"', currentGroup.group) + channels.register(currentGroup.group, { + timeout: timeout || options.groupTimeout + , alias: solo.channel }) + dbapi.enhanceStatusChangedAt(options.serial, timeout) + sub.subscribe(currentGroup.group) + push.send([ + wireutil.global + , wireutil.envelope(new wire.JoinGroupMessage(options.serial, currentGroup, usage)) + ]) + const handleRequest = (reqOptions) => { + return new Promise((resolve, reject) => { + request(reqOptions) + .then((res) => { + resolve(res) + }) + .catch((err) => { + reject(err) + }) + }) + } + dbapi.loadDeviceBySerial(options.serial).then((device) => { + // No device size/type = First device session + if (!device.display.width || !device.display.height || !device.deviceType) { + // Get device type + let deviceType + handleRequest({ + method: 'GET' + , uri: `${baseUrl}/wda/device/info` + , json: true + }) + .then((deviceInfo) => { + let deviceInfoModel = deviceInfo.value.model.toLowerCase() + let deviceInfoName = deviceInfo.value.name.toLowerCase() + if (deviceInfoModel.includes('tv') || deviceInfoName.includes('tv')) { + deviceType = 'Apple TV' + } + else { + deviceType = 'iPhone' + } + // Store device type + log.info('Storing device type value: ' + deviceType) + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceTypeMessage(options.serial, deviceType)) + ]) + }) + .catch((err) => { + log.error('Error storing device type') + return lifecycle.fatal(err) + }) + // Get device size + handleRequest({ + method: 'POST' + , uri: `${baseUrl}/session` + , body: {capabilities: {}} + , json: true, + }) + .then((sessionResponse) => { + let sessionId = sessionResponse.sessionId + // Store device version + log.info('Storing device version') + push.send([ + wireutil.global + , wireutil.envelope(new wire.SdkIosVersion(options.serial, sessionResponse.value.capabilities.device, sessionResponse.value.capabilities.sdkVersion)) + ]) + // Store battery info + if (deviceType !== 'Apple TV') { + handleRequest({ + method: 'GET' + , uri: `${baseUrl}/session/${sessionId}/wda/batteryInfo` + , json: true, + }) + .then((batteryInfoResponse) => { + let batteryState = iosutil.batteryState(batteryInfoResponse.value.state) + let batteryLevel = iosutil.batteryLevel(batteryInfoResponse.value.level) + push.send([ + wireutil.global + , wireutil.envelope(new wire.BatteryEvent(options.serial, batteryState, 'good', 'usb', batteryLevel, 1, 0.0, 5)) + ]) + }) + .catch((err) => log.error('Error storing battery', err)) + } + // Store size info + return handleRequest({ + method: 'GET' + , uri: `${baseUrl}/session/${sessionId}/window/size` + , json: true + }).then((firstSessionSize) => { + let deviceSize = firstSessionSize.value + let {width, height} = deviceSize + return handleRequest({ + method: 'GET' + , uri: `${baseUrl}/session/${sessionId}/wda/screen`, + }).then((scaleResponse) => { + let parsedResponse = JSON.parse(scaleResponse) + let scale = parsedResponse.value.scale + height *= scale + width *= scale + log.info('Storing device size/scale') + push.send([ + wireutil.global + , wireutil.envelope(new wire.SizeIosDevice(options.serial, height, width, scale)) + ]) + }) + }) + }) + .catch((err) => { + log.error('Error storing device size/scale') + return lifecycle.fatal(err) + }) + } + }) + plugin.emit('join', currentGroup) + return currentGroup }) + } + plugin.keepalive = () => { + if (currentGroup) { + channels.keepalive(currentGroup.group) } - dbapi.loadDeviceBySerial(options.serial).then((device) => { - // No device size/type = First device session - if (!device.display.width || !device.display.height || !device.deviceType) { - // Get device type - let deviceType - handleRequest({ - method: 'GET' - , uri: `${baseUrl}/wda/device/info` - , json: true + } + plugin.leave = (reason) => { + return plugin.get() + .then(group => { + log.important('No longer owned by "%s"', group.email) + log.info('Unsubscribing from group channel "%s"', group.group) + channels.unregister(group.group) + sub.unsubscribe(group.group) + push.send([ + wireutil.global + , wireutil.envelope(new wire.LeaveGroupMessage(options.serial, group, reason)) + ]) + currentGroup = null + plugin.emit('leave', group) + return group + }) + } + router + .on(wire.GroupMessage, (channel, message) => { + let reply = wireutil.reply(options.serial) + // grouputil.match(ident, message.requirements) + Promise.method(() => { + return plugin.join(message.owner, message.timeout, message.usage) + })() + .then(() => { + push.send([ + channel + , reply.okay() + ]) }) - .then((deviceInfo) => { - let deviceInfoModel = deviceInfo.value.model.toLowerCase() - let deviceInfoName = deviceInfo.value.name.toLowerCase() - if (deviceInfoModel.includes('tv') || deviceInfoName.includes('tv')) { - deviceType = 'Apple TV' - } - else { - deviceType = 'iPhone' - } - // Store device type - log.info('Storing device type value: ' + deviceType) + .catch(grouputil.RequirementMismatchError, (err) => { + push.send([ + channel + , reply.fail(err.message) + ]) + }) + .catch(grouputil.AlreadyGroupedError, (err) => { push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceTypeMessage(options.serial, deviceType)) + channel + , reply.fail(err.message) ]) }) - .catch((err) => { - log.error('Error storing device type') - return lifecycle.fatal(err) + }) + .on(wire.AutoGroupMessage, (channel, message) => { + return plugin.join(message.owner, message.timeout, message.identifier) + .then(() => { + plugin.emit('autojoin', message.identifier, true) }) - // Get device size - handleRequest({ - method: 'POST' - , uri: `${baseUrl}/session` - , body: {capabilities: {}} - , json: true, + .catch(grouputil.AlreadyGroupedError, () => { + plugin.emit('autojoin', message.identifier, false) }) - .then((sessionResponse) => { - let sessionId = sessionResponse.sessionId - // Store device version - log.info('Storing device version') + }) + .on(wire.UngroupMessage, (channel, message) => { + let reply = wireutil.reply(options.serial) + Promise.method(() => { + return plugin.leave('ungroup_request') + })() + .then(() => { push.send([ - wireutil.global - , wireutil.envelope(new wire.SdkIosVersion(options.serial, sessionResponse.value.capabilities.device, sessionResponse.value.capabilities.sdkVersion)) + channel + , reply.okay() ]) - // Store battery info - if (deviceType !== 'Apple TV') { - handleRequest({ - method: 'GET' - , uri: `${baseUrl}/session/${sessionId}/wda/batteryInfo` - , json: true, - }) - .then((batteryInfoResponse) => { - let batteryState = iosutil.batteryState(batteryInfoResponse.value.state) - let batteryLevel = iosutil.batteryLevel(batteryInfoResponse.value.level) - push.send([ - wireutil.global - , wireutil.envelope(new wire.BatteryIosEvent(options.serial, 'good', 'usb', batteryState, batteryLevel, 'n/a', 100)) - ]) - }) - .catch((err) => log.error('Error storing battery', err)) - } - // Store size info - return handleRequest({ - method: 'GET' - , uri: `${baseUrl}/session/${sessionId}/window/size` - , json: true - }).then((firstSessionSize) => { - let deviceSize = firstSessionSize.value - let {width, height} = deviceSize - return handleRequest({ - method: 'GET' - , uri: `${baseUrl}/session/${sessionId}/wda/screen`, - }).then((scaleResponse) => { - let parsedResponse = JSON.parse(scaleResponse) - let scale = parsedResponse.value.scale - height *= scale - width *= scale - log.info('Storing device size/scale') - push.send([ - wireutil.global - , wireutil.envelope(new wire.SizeIosDevice(options.serial, height, width, scale)) - ]) - }) - }) }) - .catch((err) => { - log.error('Error storing device size/scale') - return lifecycle.fatal(err) + .catch(grouputil.NoGroupError, err => { + push.send([ + channel + , reply.fail(err.message) + ]) }) - } }) - plugin.emit('join', currentGroup) - return currentGroup - }) - } - plugin.keepalive = () => { - if (currentGroup) { - channels.keepalive(currentGroup.group) - } - } - plugin.leave = (reason) => { - return plugin.get() - .then(group => { - log.important('No longer owned by "%s"', group.email) - log.info('Unsubscribing from group channel "%s"', group.group) - channels.unregister(group.group) - sub.unsubscribe(group.group) - push.send([ - wireutil.global - , wireutil.envelope(new wire.LeaveGroupMessage(options.serial, group, reason)) - ]) - currentGroup = null - plugin.emit('leave', group) - return group - }) - } - router - .on(wire.GroupMessage, (channel, message) => { - let reply = wireutil.reply(options.serial) - // grouputil.match(ident, message.requirements) - Promise.method(() => { - return plugin.join(message.owner, message.timeout, message.usage) - })() - .then(() => { - push.send([ - channel - , reply.okay() - ]) - }) - .catch(grouputil.RequirementMismatchError, (err) => { - push.send([ - channel - , reply.fail(err.message) - ]) - }) - .catch(grouputil.AlreadyGroupedError, (err) => { - push.send([ - channel - , reply.fail(err.message) - ]) - }) - }) - .on(wire.AutoGroupMessage, (channel, message) => { - return plugin.join(message.owner, message.timeout, message.identifier) - .then(() => { - plugin.emit('autojoin', message.identifier, true) - }) - .catch(grouputil.AlreadyGroupedError, () => { - plugin.emit('autojoin', message.identifier, false) - }) - }) - .on(wire.UngroupMessage, (channel, message) => { - let reply = wireutil.reply(options.serial) - Promise.method(() => { - return plugin.leave('ungroup_request') - })() - .then(() => { - push.send([ - channel - , reply.okay() - ]) + channels.on('timeout', channel => { + if (currentGroup && channel === currentGroup.group) { + plugin.leave('automatic_timeout') + } }) - .catch(grouputil.NoGroupError, err => { - push.send([ - channel - , reply.fail(err.message) - ]) + lifecycle.observe(() => { + return plugin.leave('device_absent') + .catch(grouputil.NoGroupError, () => true) }) + return plugin }) - channels.on('timeout', channel => { - if (currentGroup && channel === currentGroup.group) { - plugin.leave('automatic_timeout') - } - }) - lifecycle.observe(() => { - return plugin.leave('device_absent') - .catch(grouputil.NoGroupError, () => true) - }) - return plugin -}) diff --git a/lib/units/ios-device/plugins/heartbeat.js b/lib/units/ios-device/plugins/heartbeat.js index e55d467abe..b4083bbee2 100755 --- a/lib/units/ios-device/plugins/heartbeat.js +++ b/lib/units/ios-device/plugins/heartbeat.js @@ -6,12 +6,12 @@ import push from '../../base-device/support/push.js' export default syrup.serial() .dependency(push) .define((options, push) => { - function beat() { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceHeartbeatMessage(options.serial)) - ]) - } - let timer = setInterval(beat, options.heartbeatInterval) - lifecycle.observe(() => clearInterval(timer)) -}) + function beat() { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceHeartbeatMessage(options.serial)) + ]) + } + let timer = setInterval(beat, options.heartbeatInterval) + lifecycle.observe(() => clearInterval(timer)) + }) diff --git a/lib/units/ios-device/plugins/info/index.js b/lib/units/ios-device/plugins/info/index.js index ee498e9abe..6dbc64a86a 100644 --- a/lib/units/ios-device/plugins/info/index.js +++ b/lib/units/ios-device/plugins/info/index.js @@ -7,28 +7,28 @@ import push from '../../../base-device/support/push.js' export default syrup.serial() .dependency(push) .define((options, push) => { - const log = logger.createLogger('device:info') - function manageDeviceInfo() { - return new Promise((resolve, reject) => { - log.info('device.name: ' + options.deviceName) - let solo = wireutil.makePrivateChannel() - let osName = 'iOS' - let deviceName = options.deviceName.toLowerCase() - if (deviceName.includes('tv')) { - osName = 'tvOS' - } - push.send([ - wireutil.global - , wireutil.envelope(new wire.InitializeIosDeviceState(options.serial, wireutil.toDeviceStatus('device'), new wire.ProviderIosMessage(solo, options.provider, options.screenWsUrlPattern || ''), new wire.IosDevicePorts(options.screenPort, options.mjpegPort), new wire.UpdateIosDevice(options.serial, options.deviceName, osName, // TODO: support watchOS correctly - osName))) - ]) - return resolve() - }) - .catch(err => { - return log.error(err, 'Failed to manage device info') - }) - } - return { - manageDeviceInfo - } -}) + const log = logger.createLogger('device:info') + function manageDeviceInfo() { + return new Promise((resolve, reject) => { + log.info('device.name: ' + options.deviceName) + let solo = wireutil.makePrivateChannel() + let osName = 'iOS' + let deviceName = options.deviceName.toLowerCase() + if (deviceName.includes('tv')) { + osName = 'tvOS' + } + push.send([ + wireutil.global + , wireutil.envelope(new wire.InitializeIosDeviceState(options.serial, wireutil.toDeviceStatus('device'), new wire.ProviderIosMessage(solo, options.provider, options.screenWsUrlPattern || ''), new wire.IosDevicePorts(options.screenPort, options.mjpegPort), new wire.UpdateIosDevice(options.serial, options.deviceName, osName, // TODO: support watchOS correctly + osName))) + ]) + return resolve() + }) + .catch(err => { + return log.error(err, 'Failed to manage device info') + }) + } + return { + manageDeviceInfo + } + }) diff --git a/lib/units/ios-device/plugins/install.js b/lib/units/ios-device/plugins/install.js index 36cb53162b..71494aa998 100755 --- a/lib/units/ios-device/plugins/install.js +++ b/lib/units/ios-device/plugins/install.js @@ -70,103 +70,103 @@ export default syrup.serial() .dependency(router) .dependency(push) .define(function(options, router, push) { - const log = logger.createLogger('device:plugins:install') - router.on(wire.InstallMessage, function(channel, message) { - log.info('Installing application from "%s"', message.href) - var manifest = JSON.parse(message.manifest) - var reply = wireutil.reply(options.serial) - function sendProgress(data, progress) { - push.send([ - channel - , reply.progress(data, progress) - ]) - } - var req = request({ - url: url.resolve(options.storageUrl, message.href) - }) - // TODO: test that binary source is ipa/app/zip content - var reqInfo = new stream.Readable().wrap(req) - log.info('regInfo: ' + JSON.stringify(reqInfo)) - // save binary source content as temporary file - let id = uuid.v4() - // TODO: read from TMP env var? - let tmpDirPath = '/tmp/' + id - var filePath = tmpDirPath + '/' + id - log.info('mkdir "%s"', tmpDirPath) - mkdir(tmpDirPath) - .then(() => { - const file = fs.createWriteStream(filePath) - let httpServer = http - if (options.storageUrl.includes('https://')) { - httpServer = https + const log = logger.createLogger('device:plugins:install') + router.on(wire.InstallMessage, function(channel, message) { + log.info('Installing application from "%s"', message.href) + var manifest = JSON.parse(message.manifest) + var reply = wireutil.reply(options.serial) + function sendProgress(data, progress) { + push.send([ + channel + , reply.progress(data, progress) + ]) } - log.info('url: ', options.storageUrl.replace(/\/+$/, '') + message.href) - const req2 = httpServer.get(options.storageUrl.replace(/\/+$/, '') + message.href, function(response) { - response.pipe(file) - const totalSize = response.headers['content-length'] - let pushStart = 50 - let pushEnd = 60 - let progress - let downloadedSize = 0 - response.on('data', (chunk) => { - downloadedSize += chunk.length - sendProgress('pushing_app', progress = Math.round((downloadedSize / totalSize) * (pushEnd - pushStart) + pushStart)) - }) - // after download completed close filestream - file.on('finish', () => { - file.close() - log.info('Download Completed') - var start = 60 - var end = 95 - var guesstimate = start - sendProgress('installing_app', guesstimate) - let isSimulator = false - if (manifest !== null && manifest.DTPlatformName !== null && manifest.DTPlatformName.includes('simulator')) { - isSimulator = true + var req = request({ + url: url.resolve(options.storageUrl, message.href) + }) + // TODO: test that binary source is ipa/app/zip content + var reqInfo = new stream.Readable().wrap(req) + log.info('regInfo: ' + JSON.stringify(reqInfo)) + // save binary source content as temporary file + let id = uuid.v4() + // TODO: read from TMP env var? + let tmpDirPath = '/tmp/' + id + var filePath = tmpDirPath + '/' + id + log.info('mkdir "%s"', tmpDirPath) + mkdir(tmpDirPath) + .then(() => { + const file = fs.createWriteStream(filePath) + let httpServer = http + if (options.storageUrl.includes('https://')) { + httpServer = https } - log.info('isSimulator: ' + isSimulator) - return promiseutil.periodicNotify(installApp(options.serial, tmpDirPath, id, isSimulator), 250) - .progressed(function() { - guesstimate = Math.min(end, guesstimate + 1.5 * (end - guesstimate) / (end - start)) - // log.info("guesstimate: " + guesstimate) - sendProgress('installing_app', guesstimate) - }) - .then(function() { - if (message.launch) { - log.info('CFBundleIdentifier: ' + manifest.CFBundleIdentifier) - // Progress 90% - sendProgress('launching_app', 100) - launchApp(options.serial, manifest.CFBundleIdentifier, isSimulator) - } - }) - .then(function() { - push.send([ - channel - , reply.okay('INSTALL_SUCCEEDED') - ]) - }) - .catch(function(err) { - let errorReply = 'INSTALL_ERROR_UNKNOWN' - log.error('Installation of package "%s" failed', manifest.CFBundleIdentifier, err.stack) - if (err.stack.includes('ApplicationVerificationFailed')) { - errorReply = 'INSTALL_ERROR_APP_SIGNING' - } - push.send([ - channel - , reply.fail(errorReply) - ]) + log.info('url: ', options.storageUrl.replace(/\/+$/, '') + message.href) + const req2 = httpServer.get(options.storageUrl.replace(/\/+$/, '') + message.href, function(response) { + response.pipe(file) + const totalSize = response.headers['content-length'] + let pushStart = 50 + let pushEnd = 60 + let progress + let downloadedSize = 0 + response.on('data', (chunk) => { + downloadedSize += chunk.length + sendProgress('pushing_app', progress = Math.round((downloadedSize / totalSize) * (pushEnd - pushStart) + pushStart)) + }) + // after download completed close filestream + file.on('finish', () => { + file.close() + log.info('Download Completed') + var start = 60 + var end = 95 + var guesstimate = start + sendProgress('installing_app', guesstimate) + let isSimulator = false + if (manifest !== null && manifest.DTPlatformName !== null && manifest.DTPlatformName.includes('simulator')) { + isSimulator = true + } + log.info('isSimulator: ' + isSimulator) + return promiseutil.periodicNotify(installApp(options.serial, tmpDirPath, id, isSimulator), 250) + .progressed(function() { + guesstimate = Math.min(end, guesstimate + 1.5 * (end - guesstimate) / (end - start)) + // log.info("guesstimate: " + guesstimate) + sendProgress('installing_app', guesstimate) + }) + .then(function() { + if (message.launch) { + log.info('CFBundleIdentifier: ' + manifest.CFBundleIdentifier) + // Progress 90% + sendProgress('launching_app', 100) + launchApp(options.serial, manifest.CFBundleIdentifier, isSimulator) + } + }) + .then(function() { + push.send([ + channel + , reply.okay('INSTALL_SUCCEEDED') + ]) + }) + .catch(function(err) { + let errorReply = 'INSTALL_ERROR_UNKNOWN' + log.error('Installation of package "%s" failed', manifest.CFBundleIdentifier, err.stack) + if (err.stack.includes('ApplicationVerificationFailed')) { + errorReply = 'INSTALL_ERROR_APP_SIGNING' + } + push.send([ + channel + , reply.fail(errorReply) + ]) + }) + }) + // TODO: clear tmpDirPath }) - }) - // TODO: clear tmpDirPath - }) - }).catch(err => log.fatal('Unable to create temp dir', err)) - }) - router.on(wire.UninstallIosMessage, function(channel, message) { - log.info('UnistallIosMessage, channel', channel) - log.info('UnistallIosMessage, message', message) - let args = [ - '--id', options.serial - , '' - ] + }).catch(err => log.fatal('Unable to create temp dir', err)) + }) + router.on(wire.UninstallIosMessage, function(channel, message) { + log.info('UnistallIosMessage, channel', channel) + log.info('UnistallIosMessage, message', message) + let args = [ + '--id', options.serial + , '' + ] + }) }) -}) diff --git a/lib/units/ios-device/plugins/logger.js b/lib/units/ios-device/plugins/logger.js index 3c740bde85..e711cddf84 100755 --- a/lib/units/ios-device/plugins/logger.js +++ b/lib/units/ios-device/plugins/logger.js @@ -7,11 +7,11 @@ export default syrup.serial() .dependency(push) .define((options, push) => { // Forward all logs - logger.on('entry', entry => { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceLogMessage(options.serial, entry.timestamp / 1000, entry.priority, entry.tag, entry.pid, entry.message, entry.identifier)) - ]) + logger.on('entry', entry => { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceLogMessage(options.serial, entry.timestamp / 1000, entry.priority, entry.tag, entry.pid, entry.message, entry.identifier)) + ]) + }) + return logger }) - return logger -}) diff --git a/lib/units/ios-device/plugins/reboot.js b/lib/units/ios-device/plugins/reboot.js index 82d37461e4..72777b3bae 100755 --- a/lib/units/ios-device/plugins/reboot.js +++ b/lib/units/ios-device/plugins/reboot.js @@ -10,24 +10,24 @@ export default syrup.serial() .dependency(router) .dependency(push) .define((options, router, push) => { - const log = logger.createLogger('device:plugins:reboot') - router.on(wire.RebootMessage, (channel) => { - const reply = wireutil.reply(options.serial) - let udid = options.serial - exec(`idevicediagnostics restart --udid=${udid}`) // this command that launches restart - Promise.delay(5000) - .then(() => { - push.send([ - channel - , reply.okay() - ]) - }) - .error((err) => { - log.error('Reboot failed', err.stack) - push.send([ - channel - , reply.fail(err.message) - ]) + const log = logger.createLogger('device:plugins:reboot') + router.on(wire.RebootMessage, (channel) => { + const reply = wireutil.reply(options.serial) + let udid = options.serial + exec(`idevicediagnostics restart --udid=${udid}`) // this command that launches restart + Promise.delay(5000) + .then(() => { + push.send([ + channel + , reply.okay() + ]) + }) + .error((err) => { + log.error('Reboot failed', err.stack) + push.send([ + channel + , reply.fail(err.message) + ]) + }) }) }) -}) diff --git a/lib/units/ios-device/plugins/remotedebug.js b/lib/units/ios-device/plugins/remotedebug.js index 8fc34a1f59..fc0c0326f1 100644 --- a/lib/units/ios-device/plugins/remotedebug.js +++ b/lib/units/ios-device/plugins/remotedebug.js @@ -8,17 +8,17 @@ export default syrup.serial() .dependency(group) .dependency(push) .define((options, group, push) => { - const log = logger.createLogger('remote debug') - const updateRemoteConnectUrl = (group) => { - push.send([ - group.group - , wireutil.envelope(new wire.UpdateRemoteConnectUrl(options.serial)) - ]) - } - group.on('join', (group) => { - updateRemoteConnectUrl(group) - }) - group.on('leave', () => { + const log = logger.createLogger('remote debug') + const updateRemoteConnectUrl = (group) => { + push.send([ + group.group + , wireutil.envelope(new wire.UpdateRemoteConnectUrl(options.serial)) + ]) + } + group.on('join', (group) => { + updateRemoteConnectUrl(group) + }) + group.on('leave', () => { // do nothing + }) }) -}) diff --git a/lib/units/ios-device/plugins/screen/stream.js b/lib/units/ios-device/plugins/screen/stream.js index 5726b2c621..a83ea17275 100755 --- a/lib/units/ios-device/plugins/screen/stream.js +++ b/lib/units/ios-device/plugins/screen/stream.js @@ -1,7 +1,7 @@ import syrup from '@devicefarmer/stf-syrup' import webSocketServer from 'ws' -import * as websocketStream from 'websocket-stream' -import * as MjpegConsumer from 'mjpeg-consumer' +import websocketStream from 'websocket-stream' +import MjpegConsumer from 'mjpeg-consumer' import Promise from 'bluebird' import request from 'postman-request' import wireutil from '../../../../wire/util.js' @@ -18,96 +18,103 @@ export default syrup.serial() .dependency(wdaClient) .dependency(push) .define(function(options, solo, notifier, WdaClient, push) { - const log = logger.createLogger('device:plugins:screen:stream') - const wss = new webSocketServer.Server({port: options.screenPort}) - let url = iosutil.getUri(options.wdaHost, options.mjpegPort) - wss.on('connection', (ws) => { - ws.isAlive = true - let isConnectionAlive = true - const consumer = new MjpegConsumer() - let frameStream - let chain = Promise.resolve() - let stream = websocketStream(ws) - function handleSocketError(err, message) { - log.error(message, err) - notifier.setDeviceTemporaryUnavailable(err) - ws.close() - } - const handleRequestStream = () => { - return new Promise((resolve, reject) => { - setTimeout(() => { - frameStream = request.get(url) - frameStream.on('response', response => { - reject({response, frameStream}) - }) - frameStream.on('error', err => { - // checking for ws connection in order to stop chain promise if connection closed - if (isConnectionAlive) { - resolve() + const log = logger.createLogger('device:plugins:screen:stream') + const wss = new webSocketServer.Server({port: options.screenPort}) + let url = iosutil.getUri(options.wdaHost, options.mjpegPort) + wss.on('connection', (ws) => { + ws.isAlive = true + let isConnectionAlive = true + const consumer = new MjpegConsumer() + let frameStream = null + let chain = Promise.resolve() + let stream = websocketStream(ws) + function handleSocketError(err, message) { + log.error(message, err) + notifier.setDeviceTemporaryUnavailable(err) + ws.close() + } + const handleRequestStream = () => { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (frameStream !== null) { + frameStream.req.end() } - else { - reject() + frameStream = request.get(url) + frameStream.on('response', response => { + reject({response, frameStream}) + }) + frameStream.on('error', err => { + // checking for ws connection in order to stop chain promise if connection closed + if (isConnectionAlive) { + resolve() + } + else { + reject() + } + }) + }, 1000) + }) + } + const getRequestStream = () => { + for (let i = 0; i < 10; i++) { + chain = chain.then(() => handleRequestStream()) + } + chain + .then(() => handleSocketError({message: 'Connection failed to WDA MJPEG port'}, 'Consumer error')) + .catch(result => { + if (result) { + result.response.pipe(consumer).pipe(stream) + // [VD] We can't launch homeBtn otherwise opening in STF corrupt test automation run. Also no sense to execute connect + WdaClient.startSession() + // WdaClient.homeBtn() //no existing session detected so we can press home button to wake up device automatically + // override already existing error handler + result.frameStream.on('error', function(err) { + handleSocketError(err, 'frameStream error ') + }) } }) - }, 1000) - }) - } - const getRequestStream = () => { - for (let i = 0; i < 10; i++) { - chain = chain.then(() => handleRequestStream()) } - chain - .then(() => handleSocketError({message: 'Connection failed to WDA MJPEG port'}, 'Consumer error')) - .catch(result => { - if (result) { - result.response.pipe(consumer).pipe(stream) - // [VD] We can't launch homeBtn otherwise opening in STF corrupt test automation run. Also no sense to execute connect - WdaClient.startSession() - // WdaClient.homeBtn() //no existing session detected so we can press home button to wake up device automatically - // override already existing error handler - result.frameStream.on('error', function(err) { - handleSocketError(err, 'frameStream error ') - }) - } - }) - } - consumer.on('error', err => { - handleSocketError(err, 'Consumer error') + consumer.on('error', (err) => { + handleSocketError(err, 'Consumer error') + frameStream.req.end() // doConnectionToMJPEGStream(fn) - }) - stream.on('error', err => { + }) + stream.on('error', () => { // handleSocketError(err, 'Stream error ') - }) - stream.socket.on('error', err => { + frameStream.req.end() + }) + stream.socket.on('error', () => { // handleSocketError(err, 'Websocket stream error ') - }) - ws.on('close', function() { + frameStream.req.end() + }) + ws.on('close', function() { // @TODO handle close event // stream.socket.onclose() - const orientation = WdaClient.orientation - const stoppingSession = () => { - WdaClient.stopSession() - isConnectionAlive = false - log.important('ws on close event') - } - if (WdaClient.deviceType === 'Apple TV' || orientation === 'PORTRAIT') { - return stoppingSession() - } - // #770: Reset rotation to Portrait when closing device - const rotationPromise = new Promise((resolve, reject) => { + frameStream.req.end() + const orientation = WdaClient.orientation + const stoppingSession = () => { + WdaClient.stopSession() + isConnectionAlive = false + log.important('ws on close event') + } + if (WdaClient.deviceType === 'Apple TV' || orientation === 'PORTRAIT') { + return stoppingSession() + } + // #770: Reset rotation to Portrait when closing device + const rotationPromise = new Promise((resolve, reject) => { // Ensure that rotation is done, then stop session - WdaClient.rotation({orientation: 'PORTRAIT'}) - resolve() + WdaClient.rotation({orientation: 'PORTRAIT'}) + resolve() + }) + rotationPromise.delay(2000).then(() => stoppingSession()) }) - rotationPromise.delay(2000).then(() => stoppingSession()) - }) - ws.on('error', function() { + ws.on('error', function() { // @TODO handle error event // stream.socket.onclose() - WdaClient.stopSession() - isConnectionAlive = false - log.important('ws on error event') + WdaClient.stopSession() + isConnectionAlive = false + log.important('ws on error event') + }) + getRequestStream() }) - getRequestStream() }) -}) diff --git a/lib/units/ios-device/plugins/solo.js b/lib/units/ios-device/plugins/solo.js index e505a8fc16..9657e48cd2 100755 --- a/lib/units/ios-device/plugins/solo.js +++ b/lib/units/ios-device/plugins/solo.js @@ -13,35 +13,35 @@ export default syrup.serial() .dependency(push) .dependency(info) .define((options, sub, push, info) => { - const log = logger.createLogger('device:plugins:solo') - // The channel should keep the same value between restarts, so that - // having the client side up to date all the time is not horribly painful. - let makeChannelId = () => { - let hash = crypto.createHash('sha1') - hash.update(options.serial) - return hash.digest('base64') - } - let channel = makeChannelId() - log.info('Subscribing to permanent channel "%s"', channel) - sub.subscribe(channel) - return { - channel: channel - , poke: () => { - info.manageDeviceInfo() - .then(() => { - // wait until device will be registered - Promise.delay(3 * 1000) + const log = logger.createLogger('device:plugins:solo') + // The channel should keep the same value between restarts, so that + // having the client side up to date all the time is not horribly painful. + let makeChannelId = () => { + let hash = crypto.createHash('sha1') + hash.update(options.serial) + return hash.digest('base64') + } + let channel = makeChannelId() + log.info('Subscribing to permanent channel "%s"', channel) + sub.subscribe(channel) + return { + channel: channel + , poke: () => { + info.manageDeviceInfo() .then(() => { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceReadyMessage(options.serial, channel)) - ]) - }) - }) - .catch(err => { - log.error('catch managerinfo', err) - lifecycle.fatal(err) - }) + // wait until device will be registered + Promise.delay(3 * 1000) + .then(() => { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceReadyMessage(options.serial, channel)) + ]) + }) + }) + .catch(err => { + log.error('catch managerinfo', err) + lifecycle.fatal(err) + }) + } } - } -}) + }) diff --git a/lib/units/ios-device/plugins/util/iosutil.js b/lib/units/ios-device/plugins/util/iosutil.js index 2eb9d63003..cce3eaef35 100755 --- a/lib/units/ios-device/plugins/util/iosutil.js +++ b/lib/units/ios-device/plugins/util/iosutil.js @@ -7,137 +7,137 @@ const log = logger.createLogger('iosutil') let iosutil = { asciiparser: function(key) { switch (key) { - case 'tab': - return '\x09' - case 'enter': - return '\r' - case 'del': - return '\x08' + case 'tab': + return '\x09' + case 'enter': + return '\r' + case 'del': + return '\x08' // Disable keys (otherwise it sends the first character of key string on default case) - case 'dpad_left': - return '\v' - case 'dpad_up': - return '\0' - case 'dpad_right': - return '\f' - case 'dpad_down': - return '\x18' - case 'caps_lock': - case 'escape': - case 'home': - return null - default: - return key + case 'dpad_left': + return '\v' + case 'dpad_up': + return '\0' + case 'dpad_right': + return '\f' + case 'dpad_down': + return '\x18' + case 'caps_lock': + case 'escape': + case 'home': + return null + default: + return key } } , degreesToOrientation: function(degree) { switch (degree) { - case 0: - return 'PORTRAIT' - case 90: - return 'LANDSCAPE' - case 180: - return 'UIA_DEVICE_ORIENTATION_PORTRAIT_UPSIDEDOWN' - case 270: - return 'UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT' + case 0: + return 'PORTRAIT' + case 90: + return 'LANDSCAPE' + case 180: + return 'UIA_DEVICE_ORIENTATION_PORTRAIT_UPSIDEDOWN' + case 270: + return 'UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT' } } , orientationToDegrees: function(orientation) { switch (orientation) { - case 'PORTRAIT': - return 0 - case 'LANDSCAPE': - return 90 - case 'UIA_DEVICE_ORIENTATION_PORTRAIT_UPSIDEDOWN': - return 180 - case 'UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT': - return 270 + case 'PORTRAIT': + return 0 + case 'LANDSCAPE': + return 90 + case 'UIA_DEVICE_ORIENTATION_PORTRAIT_UPSIDEDOWN': + return 180 + case 'UIA_DEVICE_ORIENTATION_LANDSCAPERIGHT': + return 270 } } , pressButton: function(key) { switch (key) { - case 'settings': - if (this.deviceType === 'Apple TV') { - return this.appActivate('com.apple.TVSettings') - } - return this.appActivate('com.apple.Preferences') - case 'store': - if (this.deviceType === 'Apple TV') { - return this.appActivate('com.apple.TVAppStore') - } - return this.appActivate('com.apple.AppStore') - case 'volume_up': - return this.pressButton('volumeUp') - case 'volume_down': - return this.pressButton('volumeDown') - case 'power': - return this.pressPower() - case 'camera': - return this.appActivate('com.apple.camera') - case 'search': - if (this.deviceType === 'Apple TV') { - return this.appActivate('com.apple.TVSearch') - } - return this.appActivate('com.apple.mobilesafari') - case 'finder': - return this.appActivate('com.apple.findmy') - case 'home': - return this.homeBtn() - case 'mute': { - let i - for (i = 0; i < 16; i++) { - Promise.delay(1000).then(() => { - this.pressButton('volumeDown') - }) - } - return true + case 'settings': + if (this.deviceType === 'Apple TV') { + return this.appActivate('com.apple.TVSettings') } - case 'switch_charset': { - return this.switchCharset() + return this.appActivate('com.apple.Preferences') + case 'store': + if (this.deviceType === 'Apple TV') { + return this.appActivate('com.apple.TVAppStore') } - // Media button requests in case there's future WDA compatibility - case 'media_play_pause': - return log.error('Non-existent button in WDA') - case 'media_stop': - return log.error('Non-existent button in WDA') - case 'media_next': - return log.error('Non-existent button in WDA') - case 'media_previous': - return log.error('Non-existent button in WDA') - case 'media_fast_forward': - return log.error('Non-existent button in WDA') - case 'media_rewind': - return log.error('Non-existent button in WDA') - default: - return this.pressButton(key) + return this.appActivate('com.apple.AppStore') + case 'volume_up': + return this.pressButton('volumeUp') + case 'volume_down': + return this.pressButton('volumeDown') + case 'power': + return this.pressPower() + case 'camera': + return this.appActivate('com.apple.camera') + case 'search': + if (this.deviceType === 'Apple TV') { + return this.appActivate('com.apple.TVSearch') + } + return this.appActivate('com.apple.mobilesafari') + case 'finder': + return this.appActivate('com.apple.findmy') + case 'home': + return this.homeBtn() + case 'mute': { + let i + for (i = 0; i < 16; i++) { + Promise.delay(1000).then(() => { + this.pressButton('volumeDown') + }) + } + return true + } + case 'switch_charset': { + return this.switchCharset() + } + // Media button requests in case there's future WDA compatibility + case 'media_play_pause': + return log.error('Non-existent button in WDA') + case 'media_stop': + return log.error('Non-existent button in WDA') + case 'media_next': + return log.error('Non-existent button in WDA') + case 'media_previous': + return log.error('Non-existent button in WDA') + case 'media_fast_forward': + return log.error('Non-existent button in WDA') + case 'media_rewind': + return log.error('Non-existent button in WDA') + default: + return this.pressButton(key) } } , swipe: function(orientation, params, deviceSize) { switch (orientation) { - case 'PORTRAIT': - return { - fromX: params.fromX * deviceSize.width - , fromY: params.fromY * deviceSize.height - , toX: params.toX * deviceSize.width - , toY: params.toY * deviceSize.height - , duration: params.duration - } - case 'LANDSCAPE': - return { - fromX: params.fromX * deviceSize.width - , fromY: params.fromY * deviceSize.height - , toX: params.toX * deviceSize.width - , toY: params.toY * deviceSize.height - , duration: params.duration - } - default: - return { - fromX: params.fromX * deviceSize.width - , fromY: params.fromY * deviceSize.height - , toX: params.toX * deviceSize.width - , toY: params.toY * deviceSize.height - , duration: params.duration - } + case 'PORTRAIT': + return { + fromX: params.fromX * deviceSize.width + , fromY: params.fromY * deviceSize.height + , toX: params.toX * deviceSize.width + , toY: params.toY * deviceSize.height + , duration: params.duration + } + case 'LANDSCAPE': + return { + fromX: params.fromX * deviceSize.width + , fromY: params.fromY * deviceSize.height + , toX: params.toX * deviceSize.width + , toY: params.toY * deviceSize.height + , duration: params.duration + } + default: + return { + fromX: params.fromX * deviceSize.width + , fromY: params.fromY * deviceSize.height + , toX: params.toX * deviceSize.width + , toY: params.toY * deviceSize.height + , duration: params.duration + } } } , getUri: function(host, port) { @@ -145,24 +145,24 @@ let iosutil = { } , batteryState: function(state) { switch (state) { - case 0: - return 'full' - case 1: - return 'unplugged' - case 2: - return 'charging' - case 3: - return 'full' - default: - break + case 0: + return 'full' + case 1: + return 'unplugged' + case 2: + return 'charging' + case 3: + return 'full' + default: + break } } , batteryLevel: function(level) { switch (level) { - case -1: - return 100 - default: - return parseInt(level * 100, 10) + case -1: + return 100 + default: + return parseInt(level * 100, 10) } } } diff --git a/lib/units/ios-device/plugins/wda.js b/lib/units/ios-device/plugins/wda.js index 0ef40c5c98..752d948c9d 100755 --- a/lib/units/ios-device/plugins/wda.js +++ b/lib/units/ios-device/plugins/wda.js @@ -17,111 +17,111 @@ export default syrup.serial() .dependency(sub) .dependency(wdaClient) .define((options, push, sub, wdaClient) => { - const log = logger.createLogger('wda:client') - const Wda = {} - Wda.connect = () => { - sub.on('message', wirerouter() - .on(wire.KeyPressMessage, (channel, message) => { - if (wdaClient.orientation === 'LANDSCAPE' && message.key === 'home') { - wdaClient.rotation({orientation: 'PORTRAIT'}).then(() => { + const log = logger.createLogger('wda:client') + const Wda = {} + Wda.connect = () => { + sub.on('message', wirerouter() + .on(wire.KeyPressMessage, (channel, message) => { + if (wdaClient.orientation === 'LANDSCAPE' && message.key === 'home') { + wdaClient.rotation({orientation: 'PORTRAIT'}).then(() => { + iosutil.pressButton.call(wdaClient, message.key) + }) + .catch(err => { + log.error('Failed to rotate device ', err) + }) + } + // On portait mode -> home button directly iosutil.pressButton.call(wdaClient, message.key) }) - .catch(err => { - log.error('Failed to rotate device ', err) + .on(wire.StoreOpenMessage, (channel, message) => { + iosutil.pressButton.call(wdaClient, 'store') + }) + .on(wire.DashboardOpenMessage, (channel, message) => { + iosutil.pressButton.call(wdaClient, 'settings') + }) + .on(wire.PhysicalIdentifyMessage, (channel, message) => { + iosutil.pressButton.call(wdaClient, 'finder') + }) + .on(wire.TouchDownMessage, (channel, message) => { + wdaClient.tap(message) + }) + .on(wire.TapDeviceTreeElement, (channel, message) => { + wdaClient.tapDeviceTreeElement(message) + }) + .on(wire.TouchMoveIosMessage, (channel, message) => { + wdaClient.swipe(message) + }) + .on(wire.TypeMessage, (channel, message) => { + log.verbose('wire.TypeMessage: ', message) + wdaClient.typeKey({value: [iosutil.asciiparser(message.text)]}) + }) + .on(wire.KeyDownMessage, (channel, message) => { + log.verbose('wire.KeyDownMessage: ', message) + if (message.key === 'home') { + wdaClient.homeBtn() + } + else { + wdaClient.typeKey({value: [iosutil.asciiparser(message.key)]}) + } + }) + .on(wire.BrowserOpenMessage, (channel, message) => { + wdaClient.openUrl(message) }) - } - // On portait mode -> home button directly - iosutil.pressButton.call(wdaClient, message.key) - }) - .on(wire.StoreOpenMessage, (channel, message) => { - iosutil.pressButton.call(wdaClient, 'store') - }) - .on(wire.DashboardOpenMessage, (channel, message) => { - iosutil.pressButton.call(wdaClient, 'settings') - }) - .on(wire.PhysicalIdentifyMessage, (channel, message) => { - iosutil.pressButton.call(wdaClient, 'finder') - }) - .on(wire.TouchDownIosMessage, (channel, message) => { - wdaClient.tap(message) - }) - .on(wire.TapDeviceTreeElement, (channel, message) => { - wdaClient.tapDeviceTreeElement(message) - }) - .on(wire.TouchMoveIosMessage, (channel, message) => { - wdaClient.swipe(message) - }) - .on(wire.TypeMessage, (channel, message) => { - log.verbose('wire.TypeMessage: ', message) - wdaClient.typeKey({value: [iosutil.asciiparser(message.text)]}) - }) - .on(wire.KeyDownMessage, (channel, message) => { - log.verbose('wire.KeyDownMessage: ', message) - if (message.key === 'home') { - wdaClient.homeBtn() - } - else { - wdaClient.typeKey({value: [iosutil.asciiparser(message.key)]}) - } - }) - .on(wire.BrowserOpenMessage, (channel, message) => { - wdaClient.openUrl(message) - }) // WDA cannot reset browser settings/cookies yet // .on(wire.BrowserClearMessage, (channel, message) => { // wdaClient.openUrl({url: 'https://www.google.com'}) // }) - .on(wire.RotateMessage, (channel, message) => { - // #875 Ignore rotation operation while device is rotating - if (wdaClient.isRotating) { - return - } - const rotation = iosutil.degreesToOrientation(message.rotation) - wdaClient.rotation({orientation: rotation}) - .then(() => { - push.send([ - wireutil.global - , wireutil.envelope(new wire.RotationEvent(options.serial, message.rotation)) - ]) - }) - .catch(err => { - log.error('Failed to rotate device to : ', rotation, err) - }) - }) - .on(wire.TouchUpMessage, (channel, message) => { - wdaClient.touchUp() - }) - .on(wire.ScreenCaptureMessage, (channel, message) => { - wdaClient.screenshot() - .then(response => { - let reply = wireutil.reply(options.serial) - let args = { - url: url.resolve(options.storageUrl, util.format('s/upload/%s', 'image')) - } - const imageBuffer = new Buffer(response.value, 'base64') - let req = request.post(args, (err, res, body) => { - try { - let result = JSON.parse(body) - push.send([ - channel - , reply.okay('success', result.resources.file) - ]) - } - catch (err) { - log.error('Invalid JSON in response', err.stack, body) + .on(wire.RotateMessage, (channel, message) => { + // #875 Ignore rotation operation while device is rotating + if (wdaClient.isRotating) { + return } + const rotation = iosutil.degreesToOrientation(message.rotation) + wdaClient.rotation({orientation: rotation}) + .then(() => { + push.send([ + wireutil.global + , wireutil.envelope(new wire.RotationEvent(options.serial, message.rotation)) + ]) + }) + .catch(err => { + log.error('Failed to rotate device to : ', rotation, err) + }) + }) + .on(wire.TouchUpMessage, (channel, message) => { + wdaClient.touchUp() }) - req.form().append('file', imageBuffer, { - filename: util.format('%s.png', options.serial) - , contentType: 'image/png' + .on(wire.ScreenCaptureMessage, (channel, message) => { + wdaClient.screenshot() + .then(response => { + let reply = wireutil.reply(options.serial) + let args = { + url: url.resolve(options.storageUrl, util.format('s/upload/%s', 'image')) + } + const imageBuffer = new Buffer(response.value, 'base64') + let req = request.post(args, (err, res, body) => { + try { + let result = JSON.parse(body) + push.send([ + channel + , reply.okay('success', result.resources.file) + ]) + } + catch (err) { + log.error('Invalid JSON in response', err.stack, body) + } + }) + req.form().append('file', imageBuffer, { + filename: util.format('%s.png', options.serial) + , contentType: 'image/png' + }) + }) + .catch(err => { + log.error('Failed to get screenshot', err) + }) }) - }) - .catch(err => { - log.error('Failed to get screenshot', err) - }) - }) - .handler()) - return Promise.resolve() - } - return Wda -}) + .handler()) + return Promise.resolve() + } + return Wda + }) diff --git a/lib/units/ios-device/plugins/wda/WdaClient.js b/lib/units/ios-device/plugins/wda/WdaClient.js index 3034373871..06871687fb 100644 --- a/lib/units/ios-device/plugins/wda/WdaClient.js +++ b/lib/units/ios-device/plugins/wda/WdaClient.js @@ -7,7 +7,7 @@ import iosutil from '../util/iosutil.js' import wireutil from '../../../../wire/util.js' import wire from '../../../../wire/index.js' import lifecycle from '../../../../util/lifecycle.js' -import dbapi from '../../../../db/api.mjs' +import * as dbapi from '../../../../db/api.js' import devicenotifier from '../devicenotifier.js' import push from '../../../base-device/support/push.js' const LOG_REQUEST_MSG = 'Request has been sent to WDA with data: ' @@ -15,154 +15,154 @@ export default syrup.serial() .dependency(devicenotifier) .dependency(push) .define((options, notifier, push) => { - const log = logger.createLogger('wdaClient') - log.info('WdaClient.js initializing...') - const socket = new net.Socket() - const WdaClient = { - baseUrl: iosutil.getUri(options.wdaHost, options.wdaPort) - , sessionId: null - , deviceSize: null - , orientation: null - , touchDownParams: {} - , tapStartAt: 0 - , typeKeyActions: [] - , typeKeyTimerId: null - , typeKeyDelay: 250 - , upperCase: false - , isSwiping: false - , isRotating: false - , deviceType: null - , getDeviceType: function() { - if (this.deviceType !== null) { - return this.deviceType - } - return dbapi.getDeviceType(options.serial).then((deviceType) => { - if (!deviceType) { - return null + const log = logger.createLogger('wdaClient') + log.info('WdaClient.js initializing...') + const socket = new net.Socket() + const WdaClient = { + baseUrl: iosutil.getUri(options.wdaHost, options.wdaPort) + , sessionId: null + , deviceSize: null + , orientation: null + , touchDownParams: {} + , tapStartAt: 0 + , typeKeyActions: [] + , typeKeyTimerId: null + , typeKeyDelay: 250 + , upperCase: false + , isSwiping: false + , isRotating: false + , deviceType: null + , getDeviceType: function() { + if (this.deviceType !== null) { + return this.deviceType } - log.info('Reusing device type value: ', deviceType) - this.deviceType = deviceType - return this.deviceType - }).catch((err) => { - log.error('Error getting device type from DB') - return lifecycle.fatal(err) - }) - } - , startSession: function() { - log.info('verifying wda session status...') - this.getDeviceType() - const params = { - capabilities: {}, + return dbapi.getDeviceType(options.serial).then((deviceType) => { + if (!deviceType) { + return null + } + log.info('Reusing device type value: ', deviceType) + this.deviceType = deviceType + return this.deviceType + }).catch((err) => { + log.error('Error getting device type from DB') + return lifecycle.fatal(err) + }) } - return this.handleRequest({ - method: 'POST' - , uri: `${this.baseUrl}/session` - , body: params - , json: true, - }) - .then((sessionResponse) => { + , startSession: function() { + log.info('verifying wda session status...') + this.getDeviceType() + const params = { + capabilities: {}, + } return this.handleRequest({ - method: 'GET' - , uri: `${this.baseUrl}/status` + method: 'POST' + , uri: `${this.baseUrl}/session` + , body: params , json: true, }) - .then((statusResponse) => { - log.info(`status response: ${JSON.stringify(statusResponse)}`) - // handles case of existing session - if (statusResponse.sessionId) { - this.sessionId = statusResponse.sessionId - log.info(`reusing existing wda session: ${this.sessionId}`) - this.setStatus(3) - if (this.deviceType !== 'Apple TV') { - this.getOrientation() - this.batteryIosEvent() - } - this.setVersion(sessionResponse) - return this.size() - } - log.info('starting wda session...') - return this.handleRequest({ - method: 'POST' - , uri: `${this.baseUrl}/session` - , body: params - , json: true, - }) - .then((sessionResponse) => { - log.info(`startSession response: ${JSON.stringify(sessionResponse)}`) - this.setVersion(sessionResponse) - this.sessionId = sessionResponse.sessionId - log.info(`sessionId: ${this.sessionId}`) - if (this.deviceType !== 'Apple TV') { - this.getOrientation() - this.batteryIosEvent() - } - this.setStatus(3) - return this.size() - }) - .catch((err) => { - log.error('"startSession" No valid response from web driver!', err) - return Promise.reject(err) + .then((sessionResponse) => { + return this.handleRequest({ + method: 'GET' + , uri: `${this.baseUrl}/status` + , json: true, + }) + .then((statusResponse) => { + log.info(`status response: ${JSON.stringify(statusResponse)}`) + // handles case of existing session + if (statusResponse.sessionId) { + this.sessionId = statusResponse.sessionId + log.info(`reusing existing wda session: ${this.sessionId}`) + this.setStatus(3) + if (this.deviceType !== 'Apple TV') { + this.getOrientation() + this.batteryIosEvent() + } + this.setVersion(sessionResponse) + return this.size() + } + log.info('starting wda session...') + return this.handleRequest({ + method: 'POST' + , uri: `${this.baseUrl}/session` + , body: params + , json: true, + }) + .then((sessionResponse) => { + log.info(`startSession response: ${JSON.stringify(sessionResponse)}`) + this.setVersion(sessionResponse) + this.sessionId = sessionResponse.sessionId + log.info(`sessionId: ${this.sessionId}`) + if (this.deviceType !== 'Apple TV') { + this.getOrientation() + this.batteryIosEvent() + } + this.setStatus(3) + return this.size() + }) + .catch((err) => { + log.error('"startSession" No valid response from web driver!', err) + return Promise.reject(err) + }) + }) }) - }) - }) - } - , stopSession: function() { - log.info('stopping wda session: ', this.sessionId) - let currentSessionId = this.sessionId - this.sessionId = null - if (currentSessionId === null) { - return Promise.resolve() } - return this.handleRequest({ - method: 'DELETE' - , uri: `${this.baseUrl}/session/${currentSessionId}` - }) - } - , setStatus: function(status) { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceStatusMessage(options.serial, status)) - ]) - } - , typeKey: function(params) { - // collect several chars till the space and do mass actions... - if (!params.value || !params.value[0]) { - return + , stopSession: function() { + log.info('stopping wda session: ', this.sessionId) + let currentSessionId = this.sessionId + this.sessionId = null + if (currentSessionId === null) { + return Promise.resolve() + } + return this.handleRequest({ + method: 'DELETE' + , uri: `${this.baseUrl}/session/${currentSessionId}` + }) } - let [value] = params.value - // register keyDown and keyUp for current char - if (this.upperCase) { - value = value.toUpperCase() + , setStatus: function(status) { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceStatusMessage(options.serial, status)) + ]) } - this.typeKeyActions.push({type: 'keyDown', value}) - this.typeKeyActions.push({type: 'keyUp', value}) - const handleRequest = () => { - const requestParams = { - method: 'POST' - , uri: `${this.baseUrl}/session/${this.sessionId}/actions` - , body: { - actions: [ - { - type: 'key' - , id: 'keyboard' - , actions: this.typeKeyActions, - } - ] - } - , json: true, - } - // reset this.typeKeyActions array as we are going to send word or char(s) by timeout - this.typeKeyActions = [] - if (this.typeKeyTimerId) { - // reset type key timer as we are going to send word or char(s) by timeout - clearTimeout(this.typeKeyTimerId) - this.typeKeyTimerId = null + , typeKey: function(params) { + // collect several chars till the space and do mass actions... + if (!params.value || !params.value[0]) { + return } - if (this.deviceType !== 'Apple TV') { - return this.handleRequest(requestParams) + let [value] = params.value + // register keyDown and keyUp for current char + if (this.upperCase) { + value = value.toUpperCase() } - // Apple TV keys - switch (true) { + this.typeKeyActions.push({type: 'keyDown', value}) + this.typeKeyActions.push({type: 'keyUp', value}) + const handleRequest = () => { + const requestParams = { + method: 'POST' + , uri: `${this.baseUrl}/session/${this.sessionId}/actions` + , body: { + actions: [ + { + type: 'key' + , id: 'keyboard' + , actions: this.typeKeyActions, + } + ] + } + , json: true, + } + // reset this.typeKeyActions array as we are going to send word or char(s) by timeout + this.typeKeyActions = [] + if (this.typeKeyTimerId) { + // reset type key timer as we are going to send word or char(s) by timeout + clearTimeout(this.typeKeyTimerId) + this.typeKeyTimerId = null + } + if (this.deviceType !== 'Apple TV') { + return this.handleRequest(requestParams) + } + // Apple TV keys + switch (true) { case value === '\v': return this.handleRequest({ method: 'POST' @@ -200,120 +200,120 @@ export default syrup.serial() }) default: break + } } - } - if (value === ' ') { + if (value === ' ') { // as only space detected send full word to the iOS device - handleRequest() - } - else { + handleRequest() + } + else { // reset timer to start tracker again from the latest char. Final flush will happen if no types during this.typeKeyDelay ms - if (this.typeKeyTimerId) { - clearTimeout(this.typeKeyTimerId) + if (this.typeKeyTimerId) { + clearTimeout(this.typeKeyTimerId) + } + this.typeKeyTimerId = setTimeout(handleRequest, this.typeKeyDelay) } - this.typeKeyTimerId = setTimeout(handleRequest, this.typeKeyDelay) } - } - , tap: function(params) { - this.tapStartAt = (new Date()).getTime() - this.touchDownParams = params - } - , homeBtn: function() { - if (this.deviceType !== 'Apple TV') { - return this.handleRequest({ - method: 'POST' - , uri: `${this.baseUrl}/session/${this.sessionId}/wda/pressButton` - , body: {name: 'home'} - , json: true - }).then(() => { - // #801 Reset coordinates to Portrait mode after pressing home button - return this.rotation({orientation: 'PORTRAIT'}) - }) + , tap: function(params) { + this.tapStartAt = (new Date()).getTime() + this.touchDownParams = params } - else { + , homeBtn: function() { + if (this.deviceType !== 'Apple TV') { + return this.handleRequest({ + method: 'POST' + , uri: `${this.baseUrl}/session/${this.sessionId}/wda/pressButton` + , body: {name: 'home'} + , json: true + }).then(() => { + // #801 Reset coordinates to Portrait mode after pressing home button + return this.rotation({orientation: 'PORTRAIT'}) + }) + } + else { // #749: Fixing button action for AppleTV - return this.handleRequest({ - method: 'POST' - , uri: `${this.baseUrl}/session/${this.sessionId}/wda/pressButton` - , body: {name: 'menu'} - , json: true - }) - } - } - , swipe: function(params) { - const scale = iosutil.swipe(this.orientation, params, this.deviceSize) - const body = { - actions: [ - { - type: 'pointer' - , id: 'finger1' - , parameters: {pointerType: 'touch'} - , actions: [ - {type: 'pointerMove', duration: 0, x: scale.fromX, y: scale.fromY} // eslint-disable-next-line max-len - - , {type: 'pointerMove' - , duration: scale.duration * 1000 - , x: scale.toX - // eslint-disable-next-line no-nested-ternary,max-len - , y: (scale.fromY < scale.toY) ? scale.toY - (scale.toY / 4) : (scale.fromY - scale.toY >= 50 ? scale.toY + (scale.toY / 4) : scale.toY)} - , {type: 'pointerUp'} - ], - } - ], - } - if (this.deviceType === 'Apple TV') { - return log.error('Swipe is not supported') - } - let swipeOperation = () => { - if (!this.isSwiping) { - this.isSwiping = true - this.handleRequest({ + return this.handleRequest({ method: 'POST' - , uri: `${this.baseUrl}/session/${this.sessionId}/actions` - , body - , json: true, - }).then((response) => { - log.info('swipe response: ', response) - this.isSwiping = false + , uri: `${this.baseUrl}/session/${this.sessionId}/wda/pressButton` + , body: {name: 'menu'} + , json: true }) } } - return swipeOperation() - } - , touchUp: function() { - if (!this.isSwiping && this.deviceSize) { - let {x, y} = this.touchDownParams - x *= this.deviceSize.width - y *= this.deviceSize.height - if (((new Date()).getTime() - this.tapStartAt) <= 1000 || !this.tapStartAt) { - const body = { - actions: [ - { - type: 'pointer' - , id: 'finger1' - , parameters: {pointerType: 'touch'} - , actions: [ - {type: 'pointerMove', duration: 0, x, y} - , {type: 'pointerMove', duration: 0, x, y} - , {type: 'pointerUp'} - ], - } - ], - } - if (this.deviceType !== 'Apple TV') { - log.info(options.deviceName) - return this.handleRequest({ + , swipe: function(params) { + const scale = iosutil.swipe(this.orientation, params, this.deviceSize) + const body = { + actions: [ + { + type: 'pointer' + , id: 'finger1' + , parameters: {pointerType: 'touch'} + , actions: [ + {type: 'pointerMove', duration: 0, x: scale.fromX, y: scale.fromY} + + , {type: 'pointerMove' + , duration: scale.duration * 1000 + , x: scale.toX + // eslint-disable-next-line no-nested-ternary + , y: (scale.fromY < scale.toY) ? scale.toY - (scale.toY / 4) : (scale.fromY - scale.toY >= 50 ? scale.toY + (scale.toY / 4) : scale.toY)} + , {type: 'pointerUp'} + ], + } + ], + } + if (this.deviceType === 'Apple TV') { + return log.error('Swipe is not supported') + } + let swipeOperation = () => { + if (!this.isSwiping) { + this.isSwiping = true + this.handleRequest({ method: 'POST' , uri: `${this.baseUrl}/session/${this.sessionId}/actions` , body , json: true, + }).then((response) => { + log.info('swipe response: ', response) + this.isSwiping = false }) - // else if (deviceType === 'Watch_OS') {...} } - else { + } + return swipeOperation() + } + , touchUp: function() { + if (!this.isSwiping && this.deviceSize) { + let {x, y} = this.touchDownParams + x *= this.deviceSize.width + y *= this.deviceSize.height + if (((new Date()).getTime() - this.tapStartAt) <= 1000 || !this.tapStartAt) { + const body = { + actions: [ + { + type: 'pointer' + , id: 'finger1' + , parameters: {pointerType: 'touch'} + , actions: [ + {type: 'pointerMove', duration: 0, x, y} + , {type: 'pointerMove', duration: 0, x, y} + , {type: 'pointerUp'} + ], + } + ], + } + if (this.deviceType !== 'Apple TV') { + log.info(options.deviceName) + return this.handleRequest({ + method: 'POST' + , uri: `${this.baseUrl}/session/${this.sessionId}/actions` + , body + , json: true, + }) + // else if (deviceType === 'Watch_OS') {...} + } + else { // Avoid crash, wait until width/height values are available - if (x >= 0 && y >= 0) { - switch (true) { + if (x >= 0 && y >= 0) { + switch (true) { case x < 300: return this.handleRequest({ method: 'POST' @@ -349,302 +349,302 @@ export default syrup.serial() , body: {name: 'select'} , json: true, }) + } } } } + else { + if (this.deviceType === 'Apple TV') { + return log.error('Holding tap is not supported') + } + return this.handleRequest({ + method: 'POST' + , uri: `${this.baseUrl}/session/${this.sessionId}/wda/touchAndHold` + , body: {x, y, duration: 1} + , json: true, + }) + } } - else { - if (this.deviceType === 'Apple TV') { - return log.error('Holding tap is not supported') + } + , tapDeviceTreeElement: function(message) { + const params = { + using: 'link text' + , value: 'label=' + message.label, + } + return new Promise((resolve, reject) => { + this.handleRequest({ + method: 'POST' + , uri: `${this.baseUrl}/session/${this.sessionId}/elements` + , body: params + , json: true + }) + .then(response => { + const {ELEMENT} = response.value[0] + return this.handleRequest({ + method: 'POST' + , uri: `${this.baseUrl}/session/${this.sessionId}/element/${ELEMENT}/click` + , body: {} + , json: true + }) + }) + .catch(err => { + log.error(err) + }) + }) + } + , doubleClick: function() { + if (!this.isSwiping && this.deviceSize) { + const {x, y} = this.touchDownParams + const params = { + x: x * this.deviceSize.width + , y: y * this.deviceSize.height } return this.handleRequest({ method: 'POST' - , uri: `${this.baseUrl}/session/${this.sessionId}/wda/touchAndHold` - , body: {x, y, duration: 1} - , json: true, + , uri: `${this.baseUrl}/session/${this.sessionId}/wda/doubleTap` + , body: params + , json: true }) } } - } - , tapDeviceTreeElement: function(message) { - const params = { - using: 'link text' - , value: 'label=' + message.label, + , size: function() { + if (this.deviceSize !== null) { + return this.deviceSize + } + log.info('getting device window size...') + return dbapi.getDeviceDisplaySize(options.serial).then((deviceSize) => { + if (!deviceSize) { + return null + } + let dbHeight = deviceSize.height + let dbWidth = deviceSize.width + let dbScale = deviceSize.scale + if (!dbHeight || !dbWidth || !dbScale) { + return null + } + // Reuse DB values: + log.info('Reusing device size/scale') + // Set device size based on orientation, default is PORTRAIT + if (this.orientation === 'PORTRAIT' || !this.orientation) { + this.deviceSize = {height: dbHeight /= dbScale, width: dbWidth /= dbScale} + } + else if (this.orientation === 'LANDSCAPE') { + this.deviceSize = {height: dbWidth /= dbScale, width: dbHeight /= dbScale} + } + else if (this.deviceType === 'Apple TV') { + this.deviceSize = {height: dbHeight, width: dbWidth} + } + return this.deviceSize + }) + .catch((err) => { + log.error('Error getting device size from DB') + return lifecycle.fatal(err) + }) } - return new Promise((resolve, reject) => { - this.handleRequest({ + , setVersion: function(currentSession) { + log.info('Setting current device version: ' + currentSession.value.capabilities.sdkVersion) + push.send([ + wireutil.global + , wireutil.envelope(new wire.SdkIosVersion(options.serial, currentSession.value.capabilities.device, currentSession.value.capabilities.sdkVersion)) + ]) + } + , openUrl: function(message) { + const params = { + url: message.url + } + return this.handleRequest({ method: 'POST' - , uri: `${this.baseUrl}/session/${this.sessionId}/elements` + , uri: `${this.baseUrl}/session/` + this.sessionId + '/url' , body: params , json: true }) - .then(response => { - const {ELEMENT} = response.value[0] - return this.handleRequest({ - method: 'POST' - , uri: `${this.baseUrl}/session/${this.sessionId}/element/${ELEMENT}/click` - , body: {} + } + , screenshot: function() { + return new Promise((resolve, reject) => { + this.handleRequest({ + method: 'GET' + , uri: `${this.baseUrl}/screenshot` , json: true }) + .then(response => { + try { + resolve(response) + } + catch (e) { + reject(e) + } + }) + .catch(err => reject(err)) }) - .catch(err => { - log.error(err) + } + , getOrientation: function() { + return this.handleRequest({ + method: 'GET' + , uri: `${this.baseUrl}/session/${this.sessionId}/orientation` + , json: true + }).then((orientationResponse) => { + this.orientation = orientationResponse.value + log.info('Current device orientation: ' + this.orientation) }) - }) - } - , doubleClick: function() { - if (!this.isSwiping && this.deviceSize) { - const {x, y} = this.touchDownParams - const params = { - x: x * this.deviceSize.width - , y: y * this.deviceSize.height - } + } + , rotation: function(params) { + this.orientation = params.orientation + this.isRotating = true return this.handleRequest({ method: 'POST' - , uri: `${this.baseUrl}/session/${this.sessionId}/wda/doubleTap` + , uri: `${this.baseUrl}/session/${this.sessionId}/orientation` , body: params , json: true + }).then(val => { + this.getOrientation() + this.size() + const rotationDegrees = iosutil.orientationToDegrees(this.orientation) + push.send([ + wireutil.global + , wireutil.envelope(new wire.RotationEvent(options.serial, rotationDegrees)) + ]) + this.isRotating = false }) } - } - , size: function() { - if (this.deviceSize !== null) { - return this.deviceSize - } - log.info('getting device window size...') - return dbapi.getDeviceDisplaySize(options.serial).then((deviceSize) => { - if (!deviceSize) { - return null - } - let dbHeight = deviceSize.height - let dbWidth = deviceSize.width - let dbScale = deviceSize.scale - if (!dbHeight || !dbWidth || !dbScale) { - return null - } - // Reuse DB values: - log.info('Reusing device size/scale') - // Set device size based on orientation, default is PORTRAIT - if (this.orientation === 'PORTRAIT' || !this.orientation) { - this.deviceSize = {height: dbHeight /= dbScale, width: dbWidth /= dbScale} - } - else if (this.orientation === 'LANDSCAPE') { - this.deviceSize = {height: dbWidth /= dbScale, width: dbHeight /= dbScale} - } - else if (this.deviceType === 'Apple TV') { - this.deviceSize = {height: dbHeight, width: dbWidth} - } - return this.deviceSize - }) - .catch((err) => { - log.error('Error getting device size from DB') - return lifecycle.fatal(err) - }) - } - , setVersion: function(currentSession) { - log.info('Setting current device version: ' + currentSession.value.capabilities.sdkVersion) - push.send([ - wireutil.global - , wireutil.envelope(new wire.SdkIosVersion(options.serial, currentSession.value.capabilities.device, currentSession.value.capabilities.sdkVersion)) - ]) - } - , openUrl: function(message) { - const params = { - url: message.url + , batteryIosEvent: function() { + return this.handleRequest({ + method: 'GET' + , uri: `${this.baseUrl}/session/${this.sessionId}/wda/batteryInfo` + , json: true, + }) + .then((batteryInfoResponse) => { + let batteryState = iosutil.batteryState(batteryInfoResponse.value.state) + let batteryLevel = iosutil.batteryLevel(batteryInfoResponse.value.level) + push.send([ + wireutil.global + , wireutil.envelope(new wire.BatteryEvent(options.serial, batteryState, 'good', 'usb', batteryLevel, 1, 0.0, 5)) + ]) + }) + .then(() => { + log.info('Setting new device battery info') + }) + .catch((err) => log.info(err)) } - return this.handleRequest({ - method: 'POST' - , uri: `${this.baseUrl}/session/` + this.sessionId + '/url' - , body: params - , json: true - }) - } - , screenshot: function() { - return new Promise((resolve, reject) => { - this.handleRequest({ + , getTreeElements: function() { + return this.handleRequest({ method: 'GET' - , uri: `${this.baseUrl}/screenshot` + , uri: `${this.baseUrl}/source?format=json` , json: true }) - .then(response => { - try { - resolve(response) - } - catch (e) { - reject(e) + } + , pressButton: function(params) { + return this.handleRequest({ + method: 'POST' + , uri: `${this.baseUrl}/session/${this.sessionId}/wda/pressButton` + , body: { + name: params } + , json: true }) - .catch(err => reject(err)) - }) - } - , getOrientation: function() { - return this.handleRequest({ - method: 'GET' - , uri: `${this.baseUrl}/session/${this.sessionId}/orientation` - , json: true - }).then((orientationResponse) => { - this.orientation = orientationResponse.value - log.info('Current device orientation: ' + this.orientation) - }) - } - , rotation: function(params) { - this.orientation = params.orientation - this.isRotating = true - return this.handleRequest({ - method: 'POST' - , uri: `${this.baseUrl}/session/${this.sessionId}/orientation` - , body: params - , json: true - }).then(val => { - this.getOrientation() - this.size() - const rotationDegrees = iosutil.orientationToDegrees(this.orientation) - push.send([ - wireutil.global - , wireutil.envelope(new wire.RotationEvent(options.serial, rotationDegrees)) - ]) - this.isRotating = false - }) - } - , batteryIosEvent: function() { - return this.handleRequest({ - method: 'GET' - , uri: `${this.baseUrl}/session/${this.sessionId}/wda/batteryInfo` - , json: true, - }) - .then((batteryInfoResponse) => { - let batteryState = iosutil.batteryState(batteryInfoResponse.value.state) - let batteryLevel = iosutil.batteryLevel(batteryInfoResponse.value.level) - push.send([ - wireutil.global - , wireutil.envelope(new wire.BatteryIosEvent(options.serial, 'good', 'usb', batteryState, batteryLevel, 'n/a', 100)) - ]) - }) - .then(() => { - log.info('Setting new device battery info') - }) - .catch((err) => log.info(err)) - } - , getTreeElements: function() { - return this.handleRequest({ - method: 'GET' - , uri: `${this.baseUrl}/source?format=json` - , json: true - }) - } - , pressButton: function(params) { - return this.handleRequest({ - method: 'POST' - , uri: `${this.baseUrl}/session/${this.sessionId}/wda/pressButton` - , body: { - name: params - } - , json: true - }) - } - , switchCharset: function() { - this.upperCase = !this.upperCase - log.info(this.upperCase) - } - , appActivate: function(params) { - return this.handleRequest({ - method: 'POST' - , uri: `${this.baseUrl}/session/${this.sessionId}/wda/apps/activate` - , body: { - bundleId: params - } - , json: true - }) - } - , pressPower: function() { - return this.handleRequest({ - method: 'GET' - , uri: `${this.baseUrl}/session/${this.sessionId}/wda/locked` - , json: true - }) - .then(response => { - let url = '' - if (response.value === true) { - url = `${this.baseUrl}/session/${this.sessionId}/wda/unlock` - } - else { - url = `${this.baseUrl}/session/${this.sessionId}/wda/lock` - } + } + , switchCharset: function() { + this.upperCase = !this.upperCase + log.info(this.upperCase) + } + , appActivate: function(params) { return this.handleRequest({ method: 'POST' - , uri: url + , uri: `${this.baseUrl}/session/${this.sessionId}/wda/apps/activate` + , body: { + bundleId: params + } + , json: true + }) + } + , pressPower: function() { + return this.handleRequest({ + method: 'GET' + , uri: `${this.baseUrl}/session/${this.sessionId}/wda/locked` , json: true }) - }) - } - , getClipBoard: function() { - return this.handleRequest({ - method: 'POST' - , uri: `${this.baseUrl}/session/${this.sessionId}/wda/getPasteboard` - }) - .then(res => { - let clipboard = Buffer.from(JSON.parse(res).value, 'base64').toString('utf-8') - return clipboard || 'No clipboard data' - }) - } - , handleRequest: function(requestOpt) { - return new Promise((resolve, reject) => { - request(requestOpt) .then(response => { - log.verbose(LOG_REQUEST_MSG, JSON.stringify(requestOpt)) - return resolve(response) + let url = '' + if (response.value === true) { + url = `${this.baseUrl}/session/${this.sessionId}/wda/unlock` + } + else { + url = `${this.baseUrl}/session/${this.sessionId}/wda/lock` + } + return this.handleRequest({ + method: 'POST' + , uri: url + , json: true + }) + }) + } + , getClipBoard: function() { + return this.handleRequest({ + method: 'POST' + , uri: `${this.baseUrl}/session/${this.sessionId}/wda/getPasteboard` }) - .catch(err => { - let errMes = '' - if (err?.error?.value?.message) { - errMes = err.error.value.message - } - // #762 & #864: Skip lock/unlock error messages - if (errMes.includes('Timed out while waiting until the screen gets locked') || errMes.includes('unlocked')) { - return - } - // #765: Skip rotation error message - if (errMes.includes('Unable To Rotate Device')) { - return log.info('The current application does not support rotation') - } - // #770 Skip session crash, immediately restart after Portrait mode reset - if (errMes.includes('Session does not exist')) { - return this.startSession() - } - // #409: capture wda/appium crash asap and exit with status 1 from stf - // notifier.setDeviceTemporaryUnavialable(err) - notifier.setDeviceAbsent(err) - lifecycle.fatal(err) // exit with error code 1 is the best way to activate valid auto-healing steps with container(s) restart + .then(res => { + let clipboard = Buffer.from(JSON.parse(res).value, 'base64').toString('utf-8') + return clipboard || 'No clipboard data' + }) + } + , handleRequest: function(requestOpt) { + return new Promise((resolve, reject) => { + request(requestOpt) + .then(response => { + log.verbose(LOG_REQUEST_MSG, JSON.stringify(requestOpt)) + return resolve(response) + }) + .catch(err => { + let errMes = '' + if (err?.error?.value?.message) { + errMes = err.error.value.message + } + // #762 & #864: Skip lock/unlock error messages + if (errMes.includes('Timed out while waiting until the screen gets locked') || errMes.includes('unlocked')) { + return + } + // #765: Skip rotation error message + if (errMes.includes('Unable To Rotate Device')) { + return log.info('The current application does not support rotation') + } + // #770 Skip session crash, immediately restart after Portrait mode reset + if (errMes.includes('Session does not exist')) { + return this.startSession() + } + // #409: capture wda/appium crash asap and exit with status 1 from stf + // notifier.setDeviceTemporaryUnavialable(err) + notifier.setDeviceAbsent(err) + lifecycle.fatal(err) // exit with error code 1 is the best way to activate valid auto-healing steps with container(s) restart + }) }) - }) - }, - } + }, + } - /* + /* * WDA MJPEG connection is stable enough to be track status wda server itself. * As only connection is closed or error detected we have to restart STF */ - function connectToWdaMjpeg(options) { - log.info('connecting to WdaMjpeg') - socket.connect(options.mjpegPort, options.wdaHost, () => { - log.info(`Connected to WdaMjpeg ${options.wdaHost}:${options.mjpegPort}`) - }) - // #410: Use status 6 (preparing) on WDA startup - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceStatusMessage(options.serial, 6)) - ]) - } - async function wdaMjpegCloseEventHandler(hadError) { - console.log(`WdaMjpeg connection was closed${hadError ? ' by error' : ''}`) - notifier.setDeviceAbsent('WdaMjpeg connection is lost') - lifecycle.fatal('WdaMjpeg connection is lost') - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceStatusMessage(options.serial, 3)) - ]) - } - socket.on('close', wdaMjpegCloseEventHandler) - connectToWdaMjpeg(options) - return WdaClient -}) + function connectToWdaMjpeg(options) { + log.info('connecting to WdaMjpeg') + socket.connect(options.mjpegPort, options.wdaHost, () => { + log.info(`Connected to WdaMjpeg ${options.wdaHost}:${options.mjpegPort}`) + }) + // #410: Use status 6 (preparing) on WDA startup + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceStatusMessage(options.serial, 6)) + ]) + } + async function wdaMjpegCloseEventHandler(hadError) { + console.log(`WdaMjpeg connection was closed${hadError ? ' by error' : ''}`) + notifier.setDeviceAbsent('WdaMjpeg connection is lost') + lifecycle.fatal('WdaMjpeg connection is lost') + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceStatusMessage(options.serial, 3)) + ]) + } + socket.on('close', wdaMjpegCloseEventHandler) + connectToWdaMjpeg(options) + return WdaClient + }) diff --git a/lib/units/ios-device/support/storage.js b/lib/units/ios-device/support/storage.js index 5f3c574534..e801cbd83a 100755 --- a/lib/units/ios-device/support/storage.js +++ b/lib/units/ios-device/support/storage.js @@ -6,37 +6,37 @@ import request from 'postman-request' import logger from '../../../util/logger.js' export default syrup.serial() .define(options => { - const log = logger.createLogger('ios-device:support:storage') - let plugin = Object.create(null) - plugin.store = function(type, stream, meta) { - let resolver = Promise.defer() - let args = { - url: url.resolve(options.storageUrl, util.format('s/upload/%s', type)) - } - let req = request.post(args, function(err, res, body) { - if (err) { - log.error('Upload to "%s" failed', args.url, err.stack) - resolver.reject(err) - } - else if (res.statusCode !== 201) { - log.error('Upload to "%s" failed: HTTP %d', args.url, res.statusCode) - resolver.reject(new Error(util.format('Upload to "%s" failed: HTTP %d', args.url, res.statusCode))) + const log = logger.createLogger('ios-device:support:storage') + let plugin = Object.create(null) + plugin.store = function(type, stream, meta) { + let resolver = Promise.defer() + let args = { + url: url.resolve(options.storageUrl, util.format('s/upload/%s', type)) } - else { - try { - let result = JSON.parse(body) - log.info('Uploaded to "%s"', result.resources.file.href) - resolver.resolve(result.resources.file) - } - catch (err) { - log.error('Invalid JSON in response', err.stack, body) + let req = request.post(args, function(err, res, body) { + if (err) { + log.error('Upload to "%s" failed', args.url, err.stack) resolver.reject(err) } - } - }) - req.form() - .append('file', stream, meta) - return resolver.promise - } - return plugin -}) + else if (res.statusCode !== 201) { + log.error('Upload to "%s" failed: HTTP %d', args.url, res.statusCode) + resolver.reject(new Error(util.format('Upload to "%s" failed: HTTP %d', args.url, res.statusCode))) + } + else { + try { + let result = JSON.parse(body) + log.info('Uploaded to "%s"', result.resources.file.href) + resolver.resolve(result.resources.file) + } + catch (err) { + log.error('Invalid JSON in response', err.stack, body) + resolver.reject(err) + } + } + }) + req.form() + .append('file', stream, meta) + return resolver.promise + } + return plugin + }) diff --git a/lib/units/log/mongodb.js b/lib/units/log/mongodb.js index f2b72d9fcd..3db97bd0ec 100644 --- a/lib/units/log/mongodb.js +++ b/lib/units/log/mongodb.js @@ -5,7 +5,7 @@ import wirerouter from '../../wire/router.js' import wireutil from '../../wire/util.js' import lifecycle from '../../util/lifecycle.js' import srv from '../../util/srv.js' -import dbapi from '../../db/api.mjs' +import * as dbapi from '../../db/api.js' import * as zmqutil from '../../util/zmqutil.js' export default (function(options) { var log = logger.createLogger('log-db') @@ -26,10 +26,10 @@ export default (function(options) { }) sub.on('message', wirerouter() .on(wire.DeviceLogMessage, function(channel, message) { - if (message.priority >= options.priority) { - dbapi.saveDeviceLog(message.serial, message) - } - }) + if (message.priority >= options.priority) { + dbapi.saveDeviceLog(message.serial, message) + } + }) .handler()) log.info('Listening for %s (or higher) level log messages', logger.LevelLabel[options.priority]) lifecycle.observe(function() { diff --git a/lib/units/poorxy/index.js b/lib/units/poorxy/index.js index 66810a8b3a..8d2bdf70ab 100644 --- a/lib/units/poorxy/index.js +++ b/lib/units/poorxy/index.js @@ -1,5 +1,6 @@ import http from 'http' import express from 'express' +import cors from 'cors' import httpProxy from 'http-proxy' import logger from '../../util/logger.js' export default (function(options) { @@ -48,6 +49,11 @@ export default (function(options) { }) }) }) + app.use(cors({ + origin: 'http://localhost:5173' + , credentials: true + , optionsSuccessStatus: 200 + })) app.use(function(req, res) { proxy.web(req, res, { target: options.appUrl diff --git a/lib/units/processor/index.js b/lib/units/processor/index.js index 3a3b24f359..100561e11a 100644 --- a/lib/units/processor/index.js +++ b/lib/units/processor/index.js @@ -3,11 +3,12 @@ import logger from '../../util/logger.js' import wire from '../../wire/index.js' import wirerouter from '../../wire/router.js' import wireutil from '../../wire/util.js' -import * as db from '../../db/index.mjs' -import dbapi from '../../db/api.mjs' +import * as db from '../../db/index.js' +import * as dbapi from '../../db/api.js' import lifecycle from '../../util/lifecycle.js' import srv from '../../util/srv.js' import * as zmqutil from '../../util/zmqutil.js' +import {loadDeviceBySerial} from '../../db/api.js' export default db.ensureConnectivity(function(options) { const log = logger.createLogger('processor') if (options.name) { @@ -46,327 +47,245 @@ export default db.ensureConnectivity(function(options) { }) devDealer.on('message', wirerouter() .on(wire.DeviceIntroductionMessage, function(channel, message, data) { - dbapi.saveDeviceInitialState(message.serial, message).then(function() { - devDealer.send([ - message.provider.channel - , wireutil.envelope(new wire.DeviceRegisteredMessage(message.serial)) - ]) - appDealer.send([channel, data]) + dbapi.saveDeviceInitialState(message.serial, message).then(function() { + devDealer.send([ + message.provider.channel + , wireutil.envelope(new wire.DeviceRegisteredMessage(message.serial)) + ]) + appDealer.send([channel, data]) + }) }) - }) .on(wire.UpdateAccessTokenMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.DeleteUserMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.DeviceChangeMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.UserChangeMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.GroupChangeMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.DeviceGroupChangeMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.GroupUserChangeMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) - // Initial device message - /* .on(wire.DeviceIntroductionMessage, function(channel, message) { - dbapi.saveDeviceInitialState(message.serial, message) - .then(function(device) { - devDealer.send([ - message.provider.channel - , wireutil.envelope(new wire.DeviceRegisteredMessage(message.serial)) - ]) - appDealer.send([ - channel - , wireutil.envelope(new wire.DeviceIntroductionMessage( - message.serial - , message.status - , new wire.ProviderMessage( - message.provider.channel - , message.provider.name - ) - , new wire.DeviceGroupMessage( - device.group.id - , device.group.name - , new wire.DeviceGroupOwnerMessage( - device.group.owner.email - , device.group.owner.name - ) - , new wire.DeviceGroupLifetimeMessage( - device.group.lifeTime.start.getTime() - , device.group.lifeTime.stop.getTime() - ) - , device.group.class - , device.group.repetitions - , device.group.originName - ) - )) - ]) - }) - .catch(function(err) { - log.error( - 'Unable to save the initial state of Device "%s"' - , message.serial - , err.stack - ) - }) - })*/ + appDealer.send([channel, data]) + }) .on(wire.InitializeIosDeviceState, function(channel, message, data) { - dbapi.initializeIosDeviceState(options.publicIp, message) - }) + dbapi.initializeIosDeviceState(options.publicIp, message) + }) // Workerless messages .on(wire.DevicePresentMessage, function(channel, message, data) { - dbapi.setDevicePresent(message.serial) - appDealer.send([channel, data]) - }) - // Worker initialized - // .on(wire.DeviceReadyMessage, function(channel, message, data) { - // dbapi.setDeviceReady(message.serial, message.channel) - // .then(function() { - // devDealer.send([ - // message.channel - // , wireutil.envelope(new wire.DeviceRegisteredMessage(message.serial)) - // ]) - // appDealer.send([channel, data]) - // }) - // }) - // Workerless messages - .on(wire.DeviceAbsentMessage, function(channel, message, data) { - if (!message.applications) { - dbapi.setDeviceAbsent(message.serial) + dbapi.setDevicePresent(message.serial) appDealer.send([channel, data]) - } - }) + }) + .on(wire.DeviceAbsentMessage, function(channel, message, data) { + if (!message.applications) { + dbapi.setDeviceAbsent(message.serial) + appDealer.send([channel, data]) + } + }) .on(wire.DeviceStatusMessage, function(channel, message, data) { - dbapi.saveDeviceStatus(message.serial, message.status) - appDealer.send([channel, data]) - }) + dbapi.saveDeviceStatus(message.serial, message.status) + appDealer.send([channel, data]) + }) .on(wire.DeviceHeartbeatMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) // Worker initialized .on(wire.DeviceReadyMessage, function(channel, message, data) { - dbapi.setDeviceReady(message.serial, message.channel).then(function() { - devDealer.send([message.channel, wireutil.envelope(new wire.ProbeMessage())]) - appDealer.send([channel, data]) + dbapi.setDeviceReady(message.serial, message.channel).then(function() { + devDealer.send([message.channel, wireutil.envelope(new wire.ProbeMessage())]) + appDealer.send([channel, data]) + }) }) - }) .on(wire.DeviceOnInstAppMessage, function(channel, message) { - dbapi - .getInstalledApplications(message) - .then(device => { - log.info('DeviceOnInstAppMessage, channel : ', channel) - log.info('DeviceOnInstAppMessage, channel : ', new Buffer(device.channel)) - const {installedApps} = device - appDealer.send([ - new Buffer(device.owner.group) - , wireutil.envelope(new wire.InstalledApplications(message.serial, wireutil.toInstalledApps(installedApps))) - ]) - }) - .catch(err => { - log.error(err) + dbapi + .getInstalledApplications(message) + .then(device => { + log.info('DeviceOnInstAppMessage, channel : ', channel) + log.info('DeviceOnInstAppMessage, channel : ', new Buffer(device.channel)) + const {installedApps} = device + appDealer.send([ + new Buffer(device.owner.group) + , wireutil.envelope(new wire.InstalledApplications(message.serial, wireutil.toInstalledApps(installedApps))) + ]) + }) + .catch(err => { + log.error(err) + }) }) - }) // Worker messages .on(wire.JoinGroupByAdbFingerprintMessage, function(channel, message) { - dbapi - .lookupUserByAdbFingerprint(message.fingerprint) - .then(function(user) { - if (user) { - devDealer.send([ - channel - , wireutil.envelope(new wire.AutoGroupMessage(new wire.OwnerMessage(user.email, user.name, user.group), message.fingerprint)) - ]) - } - else if (message.currentGroup) { - appDealer.send([ - message.currentGroup - , wireutil.envelope(new wire.JoinGroupByAdbFingerprintMessage(message.serial, message.fingerprint, message.comment)) - ]) - } - }) - .catch(function(err) { - log.error('Unable to lookup user by ADB fingerprint "%s"', message.fingerprint, err.stack) + dbapi + .lookupUserByAdbFingerprint(message.fingerprint) + .then(function(user) { + if (user) { + devDealer.send([ + channel + , wireutil.envelope(new wire.AutoGroupMessage(new wire.OwnerMessage(user.email, user.name, user.group), message.fingerprint)) + ]) + } + else if (message.currentGroup) { + appDealer.send([ + message.currentGroup + , wireutil.envelope(new wire.JoinGroupByAdbFingerprintMessage(message.serial, message.fingerprint, message.comment)) + ]) + } + }) + .catch(function(err) { + log.error('Unable to lookup user by ADB fingerprint "%s"', message.fingerprint, err.stack) + }) }) - }) .on(wire.JoinGroupByVncAuthResponseMessage, function(channel, message) { - dbapi - .lookupUserByVncAuthResponse(message.response, message.serial) - .then(function(user) { - if (user) { - devDealer.send([ - channel - , wireutil.envelope(new wire.AutoGroupMessage(new wire.OwnerMessage(user.email, user.name, user.group), message.response)) - ]) - } - else if (message.currentGroup) { - appDealer.send([ - message.currentGroup - , wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage(message.serial, message.response)) - ]) - } + dbapi + .lookupUserByVncAuthResponse(message.response, message.serial) + .then(function(user) { + if (user) { + devDealer.send([ + channel + , wireutil.envelope(new wire.AutoGroupMessage(new wire.OwnerMessage(user.email, user.name, user.group), message.response)) + ]) + } + else if (message.currentGroup) { + appDealer.send([ + message.currentGroup + , wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage(message.serial, message.response)) + ]) + } + }) + .catch(function(err) { + log.error('Unable to lookup user by VNC auth response "%s"', message.response, err.stack) + }) }) - .catch(function(err) { - log.error('Unable to lookup user by VNC auth response "%s"', message.response, err.stack) - }) - }) .on(wire.ConnectStartedMessage, function(channel, message, data) { - dbapi.setDeviceConnectUrl(message.serial, message.url) - appDealer.send([channel, data]) - }) + dbapi.setDeviceConnectUrl(message.serial, message.url) + appDealer.send([channel, data]) + }) .on(wire.ConnectStoppedMessage, function(channel, message, data) { - dbapi.unsetDeviceConnectUrl(message.serial) - appDealer.send([channel, data]) - }) + dbapi.unsetDeviceConnectUrl(message.serial) + appDealer.send([channel, data]) + }) .on(wire.JoinGroupMessage, function(channel, message, data) { - dbapi.setDeviceOwner(message.serial, message.owner) - if (message.usage) { - dbapi.setDeviceUsage(message.serial, message.usage) - } - appDealer.send([channel, data]) - }) + dbapi.setDeviceOwner(message.serial, message.owner) + if (message.usage) { + dbapi.setDeviceUsage(message.serial, message.usage) + } + appDealer.send([channel, data]) + }) .on(wire.LeaveGroupMessage, function(channel, message, data) { - dbapi.unsetDeviceOwner(message.serial, message.owner) - dbapi.unsetDeviceUsage(message.serial) - appDealer.send([channel, data]) - }) + dbapi.unsetDeviceOwner(message.serial, message.owner) + dbapi.unsetDeviceUsage(message.serial) + appDealer.send([channel, data]) + }) .on(wire.DeviceLogMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.DeviceIdentityMessage, function(channel, message, data) { - dbapi.saveDeviceIdentity(message.serial, message) - appDealer.send([channel, data]) - }) + dbapi.saveDeviceIdentity(message.serial, message) + appDealer.send([channel, data]) + }) .on(wire.TransactionProgressMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.TransactionDoneMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.TransactionTreeMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.DeviceLogcatEntryMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.TemporarilyUnavailableMessage, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.UpdateRemoteConnectUrl, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.InstalledApplications, function(channel, message, data) { - appDealer.send([channel, data]) - }) + appDealer.send([channel, data]) + }) .on(wire.AirplaneModeEvent, function(channel, message, data) { - dbapi.setDeviceAirplaneMode(message.serial, message.enabled) - appDealer.send([channel, data]) - }) + dbapi.setDeviceAirplaneMode(message.serial, message.enabled) + appDealer.send([channel, data]) + }) .on(wire.BatteryEvent, function(channel, message, data) { - dbapi.setDeviceBattery(message.serial, message) - appDealer.send([channel, data]) - }) + dbapi.setDeviceBattery(message.serial, message) + appDealer.send([channel, data]) + }) .on(wire.DeviceBrowserMessage, function(channel, message, data) { - dbapi.setDeviceBrowser(message.serial, message) - appDealer.send([channel, data]) - }) + dbapi.setDeviceBrowser(message.serial, message) + appDealer.send([channel, data]) + }) .on(wire.ConnectivityEvent, function(channel, message, data) { - dbapi.setDeviceConnectivity(message.serial, message) - appDealer.send([channel, data]) - }) + dbapi.setDeviceConnectivity(message.serial, message) + appDealer.send([channel, data]) + }) .on(wire.PhoneStateEvent, function(channel, message, data) { - dbapi.setDevicePhoneState(message.serial, message) - appDealer.send([channel, data]) - }) - .on(wire.RotationEvent, function(channel, message, data) { - dbapi.setDeviceRotation(message.serial, message.rotation) - appDealer.send([channel, data]) - }) - .on(wire.RotationIosEvent, function(channel, message, data) { - dbapi.setIosDeviceRotation(message) - }) - .on(wire.ReverseForwardsEvent, function(channel, message, data) { - dbapi.setDeviceReverseForwards(message.serial, message.forwards) - appDealer.send([channel, data]) - }) - .on(wire.SetIosDeviceDisplay, function(channel, message, data) { - dbapi - .setDeviceSocketDisplay(message) - .then(function(response) { - log.info('setDeviceSocketDisplay response', response) + dbapi.setDevicePhoneState(message.serial, message) + appDealer.send([channel, data]) }) - .catch(function(err) { - log.error('setDeviceSocketDisplay', err) + .on(wire.RotationEvent, function(channel, message, data) { + dbapi.setDeviceRotation(message) + appDealer.send([channel, data]) }) - }) - .on(wire.CheckIosDeviceConnected, function(channel, message) { - dbapi - .checkIosDeviceConnected(message) - .then(condition => { - if (condition === null || condition.present === false) { - devDealer.send([message.channel - , wireutil.envelope(new wire.ConnectDeviceViaUSB(message.id)) - ]) - } + .on(wire.ReverseForwardsEvent, function(channel, message, data) { + dbapi.setDeviceReverseForwards(message.serial, message.forwards) + appDealer.send([channel, data]) }) - .catch(err => { - log.info(err) + .on(wire.SetDeviceDisplay, function(channel, message, data) { + dbapi + .setDeviceSocketDisplay(message) + .then(function(response) { + log.info('setDeviceSocketDisplay response', response) + }) + .catch(function(err) { + log.error('setDeviceSocketDisplay', err) + }) }) - }) .on(wire.UpdateIosDevice, function(channel, message, data) { - dbapi - .updateIosDevice(message) - .then(result => { - log.info(result) - }) - .catch(err => { - log.info(err) - }) - }) - .on(wire.BatteryIosEvent, function(channel, message, data) { - dbapi - .setDeviceIosBattery(message) - .then(result => { - log.info(result) + dbapi + .updateIosDevice(message) + .then(result => { + log.info(result) + }) + .catch(err => { + log.info(err) + }) }) - .catch(err => { - log.info(err) - }) - }) .on(wire.SdkIosVersion, function(channel, message, data) { - dbapi - .setDeviceIosVersion(message) - .then(result => { - log.info(result) - }) - .catch(err => { - log.info(err) + dbapi + .setDeviceIosVersion(message) + .then(result => { + log.info(result) + }) + .catch(err => { + log.info(err) + }) }) - }) .on(wire.SizeIosDevice, function(channel, message, data) { - dbapi.sizeIosDevice(message.id, message.height, message.width, message.scale).then(result => { - log.info(result) - }).catch(err => { - log.info(err) + dbapi.sizeIosDevice(message.id, message.height, message.width, message.scale).then(result => { + log.info(result) + }).catch(err => { + log.info(err) + }) + appDealer.send([channel, data]) }) - appDealer.send([channel, data]) - }) .on(wire.DeviceTypeMessage, function(channel, message, data) { - dbapi.setDeviceType(message.serial, message.type) - }) - .on(wire.DeleteIosDevice, function(channel, message) { - dbapi.deleteDevice(message) - }) + dbapi.setDeviceType(message.serial, message.type) + }) + .on(wire.DeleteDevice, function(channel, message) { + dbapi.deleteDevice(message.serial) + }) .on(wire.SetAbsentDisconnectedDevices, function(channel, message) { - dbapi.setAbsentDisconnectedDevices() - }) + dbapi.setAbsentDisconnectedDevices() + }) /* .on(wire.SetDeviceApp, function(channel, message) { dbapi @@ -382,9 +301,9 @@ export default db.ensureConnectivity(function(options) { }) }) */ .on(wire.GetServicesAvailabilityMessage, function(channel, message, data) { - dbapi.setDeviceServicesAvailability(message.serial, message) - appDealer.send([channel, data]) - }) + dbapi.setDeviceServicesAvailability(message.serial, message) + appDealer.send([channel, data]) + }) .handler()) lifecycle.observe(function() { [appDealer, devDealer].forEach(function(sock) { diff --git a/lib/units/provider/index.js b/lib/units/provider/index.js index 5aba1d7d66..7ae6ca6fd4 100644 --- a/lib/units/provider/index.js +++ b/lib/units/provider/index.js @@ -58,9 +58,9 @@ export default (function(options) { }) }) .catch(function(err) { - log.fatal('Unable to connect to push endpoint', err) - lifecycle.fatal() - }) + log.fatal('Unable to connect to push endpoint', err) + lifecycle.fatal() + }) // Input var sub = zmqutil.socket('sub') Promise.map(options.endpoints.sub, function(endpoint) { @@ -73,9 +73,9 @@ export default (function(options) { }) }) .catch(function(err) { - log.fatal('Unable to connect to sub endpoint', err) - lifecycle.fatal() - }); + log.fatal('Unable to connect to sub endpoint', err) + lifecycle.fatal() + }); [solo].forEach(function(channel) { log.info('Subscribing to permanent channel "%s"', channel) sub.subscribe(channel) @@ -161,13 +161,13 @@ export default (function(options) { } function messageListener(message) { switch (message) { - case 'ready': - _.pull(lists.waiting, device.id) - lists.ready.push(device.id) - break - default: - log.warn('Unknown message from device worker "%s": "%s"', device.id, message) - break + case 'ready': + _.pull(lists.waiting, device.id) + lists.ready.push(device.id) + break + default: + log.warn('Unknown message from device worker "%s": "%s"', device.id, message) + break } } proc.on('exit', exitListener) @@ -177,51 +177,51 @@ export default (function(options) { return resolver.promise .cancellable() .finally(function() { - log.info('Cleaning up device worker "%s"', device.id) - proc.removeListener('exit', exitListener) - proc.removeListener('error', errorListener) - proc.removeListener('message', messageListener) - // Return used ports to the main pool - Array.prototype.push.apply(ports, allocatedPorts) - // Update lists - _.pull(lists.ready, device.id) - _.pull(lists.waiting, device.id) - }) + log.info('Cleaning up device worker "%s"', device.id) + proc.removeListener('exit', exitListener) + proc.removeListener('error', errorListener) + proc.removeListener('message', messageListener) + // Return used ports to the main pool + Array.prototype.push.apply(ports, allocatedPorts) + // Update lists + _.pull(lists.ready, device.id) + _.pull(lists.waiting, device.id) + }) .catch(Promise.CancellationError, function() { - if (!didExit) { - log.info('Gracefully killing device worker "%s"', device.id) - return procutil.gracefullyKill(proc, options.killTimeout) - } - }) + if (!didExit) { + log.info('Gracefully killing device worker "%s"', device.id) + return procutil.gracefullyKill(proc, options.killTimeout) + } + }) .catch(Promise.TimeoutError, function(err) { - log.error('Device worker "%s" did not stop in time: %s', device.id, err.message) - }) + log.error('Device worker "%s" did not stop in time: %s', device.id, err.message) + }) } // Starts a device worker and keeps it alive function work() { log.info('Starting to work') return (worker = workers[device.id] = spawn()) .then(function() { - log.info('Device worker "%s" has retired', device.id) - delete workers[device.id] - worker = null - // Tell others the device is gone - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceAbsentMessage(device.id)) - ]) - }) + log.info('Device worker "%s" has retired', device.id) + delete workers[device.id] + worker = null + // Tell others the device is gone + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceAbsentMessage(device.id)) + ]) + }) .catch(procutil.ExitError, function(err) { - if (!willStop) { - log.error(err) - log.error('Device worker "%s" died with code %s', device.id, err.code) - log.info('Restarting device worker "%s"', device.id) - return Promise.delay(500) - .then(function() { - return work() - }) - } - }) + if (!willStop) { + log.error(err) + log.error('Device worker "%s" died with code %s', device.id, err.code) + log.info('Restarting device worker "%s"', device.id) + return Promise.delay(500) + .then(function() { + return work() + }) + } + }) } // No more work required function stop() { @@ -238,15 +238,15 @@ export default (function(options) { // We might get multiple status updates in rapid succession, // so let's wait for a while switch (device.type) { - case 'device': - case 'emulator': - willStop = false - timer = setTimeout(work, 100) - break - default: - willStop = true - timer = setTimeout(stop, 100) - break + case 'device': + case 'emulator': + willStop = false + timer = setTimeout(work, 100) + break + default: + willStop = true + timer = setTimeout(stop, 100) + break } } else { @@ -317,8 +317,8 @@ export default (function(options) { })) sub.on('message', wirerouter() .on(wire.DeviceRegisteredMessage, function(channel, message) { - flippedTracker.emit(message.serial, 'register') - }) + flippedTracker.emit(message.serial, 'register') + }) .handler()) lifecycle.share('Tracker', tracker) }) diff --git a/lib/units/ratelimit/index.js b/lib/units/ratelimit/index.js index 7b16f4e5f4..45adbef662 100644 --- a/lib/units/ratelimit/index.js +++ b/lib/units/ratelimit/index.js @@ -1,11 +1,11 @@ import {rateLimit} from 'express-rate-limit' let rateLimitConfig = rateLimit({ - windowMs: 60 * 1000 // 1 minute - , limit: 140 * 20 - , standardHeaders: 'draft-7' - , legacyHeaders: false // Disable the `X-RateLimit-*` headers - , keyGenerator: (req, res) => { - return req.headers['X-Real-IP'] ? req.headers['X-Real-IP'] : 'localhost' - } + windowMs: 60 * 1000 // 1 minute + , limit: 140 * 20 + , standardHeaders: 'draft-7' + , legacyHeaders: false // Disable the `X-RateLimit-*` headers + , keyGenerator: (req, res) => { + return req.headers['X-Real-IP'] ? req.headers['X-Real-IP'] : 'localhost' + } }) export default rateLimitConfig diff --git a/lib/units/reaper/index.js b/lib/units/reaper/index.js index 9fb07310ca..5d0ddffae4 100644 --- a/lib/units/reaper/index.js +++ b/lib/units/reaper/index.js @@ -3,7 +3,7 @@ import logger from '../../util/logger.js' import wire from '../../wire/index.js' import wireutil from '../../wire/util.js' import wirerouter from '../../wire/router.js' -import dbapi from '../../db/api.mjs' +import * as dbapi from '../../db/api.js' import lifecycle from '../../util/lifecycle.js' import srv from '../../util/srv.js' import TtlSet from '../../util/ttlset.js' @@ -23,9 +23,9 @@ export default (function(options) { }) }) .catch(function(err) { - log.fatal('Unable to connect to sub endpoint', err) - lifecycle.fatal() - }); + log.fatal('Unable to connect to sub endpoint', err) + lifecycle.fatal() + }); [wireutil.global].forEach(function(channel) { log.info('Subscribing to permanent channel "%s"', channel) sub.subscribe(channel) @@ -42,9 +42,9 @@ export default (function(options) { }) }) .catch(function(err) { - log.fatal('Unable to connect to push endpoint', err) - lifecycle.fatal() - }) + log.fatal('Unable to connect to push endpoint', err) + lifecycle.fatal() + }) ttlset.on('insert', function(serial) { log.info('Device "%s" is present', serial) push.send([ @@ -62,25 +62,25 @@ export default (function(options) { function loadInitialState() { return dbapi.loadPresentDevices() .then(function(devices) { - let now = Date.now() - devices.forEach(function(device) { - ttlset.bump(device.serial, now, TtlSet.SILENT) + let now = Date.now() + devices.forEach(function(device) { + ttlset.bump(device.serial, now, TtlSet.SILENT) + }) }) - }) } function listenToChanges() { sub.on('message', wirerouter() .on(wire.DeviceIntroductionMessage, function(channel, message) { - message.status = 3 - ttlset.drop(message.serial, TtlSet.SILENT) - ttlset.bump(message.serial, Date.now()) - }) + message.status = 3 + ttlset.drop(message.serial, TtlSet.SILENT) + ttlset.bump(message.serial, Date.now()) + }) .on(wire.DeviceHeartbeatMessage, function(channel, message) { - ttlset.bump(message.serial, Date.now()) - }) + ttlset.bump(message.serial, Date.now()) + }) .on(wire.DeviceAbsentMessage, function(channel, message) { - ttlset.drop(message.serial, TtlSet.SILENT) - }) + ttlset.drop(message.serial, TtlSet.SILENT) + }) .handler()) } log.info('Reaping devices with no heartbeat') diff --git a/lib/units/storage/plugins/apk/index.js b/lib/units/storage/plugins/apk/index.js index 7b13bb332a..111c33e7d3 100644 --- a/lib/units/storage/plugins/apk/index.js +++ b/lib/units/storage/plugins/apk/index.js @@ -2,6 +2,7 @@ import http from 'http' import url from 'url' import util from 'util' import express from 'express' +import cors from 'cors' import request from 'postman-request' import logger from '../../../../util/logger.js' import download from '../../../../util/download.js' @@ -10,7 +11,7 @@ import rateLimitConfig from '../../../ratelimit/index.js' import {accessTokenAuth} from '../../../api/helpers/securityHandlers.js' import cookieSession from 'cookie-session' import csrf from 'csurf' -import apiutil from '../../../../util/apiutil.js' +import * as apiutil from '../../../../util/apiutil.js' export default (function(options) { var log = logger.createLogger('storage:plugins:apk') var app = express() @@ -29,6 +30,11 @@ export default (function(options) { } accessTokenAuth(req, res, next) }) + app.use(cors({ + origin: 'http://localhost:5173' + , credentials: true + , optionsSuccessStatus: 200 + })) app.use(csrf()) app.use(route) app.set('strict routing', true) @@ -42,22 +48,22 @@ export default (function(options) { , jwt: req.internalJwt }) .then((file) => { - log.info('Got apk from ' + downloadUrl + ' in ' + file.path) - manifest(file).then(data => { - res.status(200) - .json({ - success: true - , manifest: data + log.info('Got apk from ' + downloadUrl + ' in ' + file.path) + manifest(file).then(data => { + res.status(200) + .json({ + success: true + , manifest: data + }) }) }) - }) .catch(function(err) { - log.error('Unable to read manifest of "%s"', req.params.id, err.stack) - res.status(400) - .json({ - success: false + log.error('Unable to read manifest of "%s"', req.params.id, err.stack) + res.status(400) + .json({ + success: false + }) }) - }) }) route.get('/s/apk/:id/:name', function(req, res) { request(url.resolve(options.storageUrl, util.format('/s/blob/%s/%s', req.params.id, req.params.name)), { diff --git a/lib/units/storage/plugins/image/index.js b/lib/units/storage/plugins/image/index.js index 4001116290..b6e2b6eac3 100644 --- a/lib/units/storage/plugins/image/index.js +++ b/lib/units/storage/plugins/image/index.js @@ -1,6 +1,7 @@ import http from 'http' import util from 'util' import express from 'express' +import cors from 'cors' import logger from '../../../../util/logger.js' import * as requtil from '../../../../util/requtil.js' import parseCrop from './param/crop.js' @@ -32,31 +33,36 @@ export default (function(options) { app.set('case sensitive routing', true) app.set('trust proxy', true) app.use(csrf()) + app.use(cors({ + origin: 'http://localhost:5173' + , credentials: true + , optionsSuccessStatus: 200 + })) app.use(route) route.get('/s/image/:id/:name', requtil.limit(options.concurrency, function(req, res) { var orig = util.format('/s/blob/%s/%s', req.params.id, req.params.name) log.info('saving screenshot img', orig) return get(orig, options) .then(function(stream) { - return transform(stream, { - crop: parseCrop(req.query.crop) - , gravity: parseGravity(req.query.gravity) + return transform(stream, { + crop: parseCrop(req.query.crop) + , gravity: parseGravity(req.query.gravity) + }) }) - }) .then(function(out) { - res.status(200) - if (typeof req.query.download !== 'undefined') { - res.set('Content-Disposition', 'attachment; filename="' + req.params['0'] + '"') - } - out.pipe(res) - }) + res.status(200) + if (typeof req.query.download !== 'undefined') { + res.set('Content-Disposition', 'attachment; filename="' + req.params['0'] + '"') + } + out.pipe(res) + }) .catch(function(err) { - log.error('Unable to transform resource "%s"', req.params.id, err.stack) - res.status(500) - .json({ - success: false + log.error('Unable to transform resource "%s"', req.params.id, err.stack) + res.status(500) + .json({ + success: false + }) }) - }) })) server.listen(options.port) log.info('Listening on port %d', options.port) diff --git a/lib/units/storage/s3.js b/lib/units/storage/s3.js index 46825d8355..1f98b65c98 100644 --- a/lib/units/storage/s3.js +++ b/lib/units/storage/s3.js @@ -3,6 +3,7 @@ import util from 'util' import path from 'path' import fs from 'fs' import express from 'express' +import cors from 'cors' import validator from 'express-validator' import bodyParser from 'body-parser' import formidable from 'formidable' @@ -28,6 +29,11 @@ export default (function(options) { app.use(accessTokenAuth) app.use(rateLimitConfig) app.use(bodyParser.json()) + app.use(cors({ + origin: 'http://localhost:5173' + , credentials: true + , optionsSuccessStatus: 200 + })) app.use(validator()) function putObject(plugin, file) { return new Promise(function(resolve, reject) { @@ -62,60 +68,59 @@ export default (function(options) { var plugin = req.params.plugin Promise.promisify(form.parse, form)(req) .spread(function(fields, files) { - var requests = Object.keys(files).map(function(field) { - var file = files[field] - return putObject(plugin, file) - .then(function(id) { - log.info('Store screenshot :', file.path, file.name) - return { - field: field - , id: id - , name: file.name - , temppath: file.path - } + var requests = Object.keys(files).map(function(field) { + var file = files[field] + return putObject(plugin, file) + .then(function(id) { + log.info('Store screenshot :', file.path, file.name) + return { + field: field + , id: id + , name: file.name + , temppath: file.path + } + }) }) + return Promise.all(requests) }) - return Promise.all(requests) - }) .then(function(storedFiles) { - res.status(201).json({ - success: true - , resources: (function() { - var mapped = Object.create(null) - storedFiles.forEach(function(file) { - mapped[file.field] = { - date: new Date() - , plugin: plugin - , id: file.id - , name: file.name - , href: getHref(plugin, file.id, file.name) - } - }) - return mapped - })() + res.status(201).json({ + success: true + , resources: (function() { + var mapped = Object.create(null) + storedFiles.forEach(function(file) { + mapped[file.field] = { + date: new Date() + , plugin: plugin + , id: file.id + , name: file.name + , href: getHref(plugin, file.id, file.name) + } + }) + return mapped + })() + }) + return storedFiles }) - return storedFiles - }) .then(function(storedFiles) { - return Promise.all(storedFiles.map(function(file) { - return Promise.promisify(fs.unlink, fs)(file.temppath) - .catch(function(err) { - log.warn('Unable to clean up "%s"', file.temppath, err.stack) - return true - }) - })) - }) + return Promise.all(storedFiles.map(function(file) { + return Promise.promisify(fs.unlink, fs)(file.temppath) + .catch(function(err) { + log.warn('Unable to clean up "%s"', file.temppath, err.stack) + return true + }) + })) + }) .catch(function(err) { - log.error('Error storing resource', err.stack) - res.status(500) - .json({ - success: false - , error: 'ServerError' + log.error('Error storing resource', err.stack) + res.status(500) + .json({ + success: false + , error: 'ServerError' + }) }) - }) }) app.get('/s/blob/:id/:name', function(req, res) { - // eslint-disable-next-line no-console var params = { Key: req.params.id , Bucket: options.bucket diff --git a/lib/units/storage/temp.js b/lib/units/storage/temp.js index 929671b92e..dd55b68e87 100644 --- a/lib/units/storage/temp.js +++ b/lib/units/storage/temp.js @@ -3,6 +3,7 @@ import util from 'util' import path from 'path' import crypto from 'crypto' import express from 'express' +import cors from 'cors' import validator from 'express-validator' import bodyParser from 'body-parser' import formidable from 'formidable' @@ -37,6 +38,11 @@ export default (function(options) { accessTokenAuth(req, res, next) }) app.use(bodyParser.json()) + app.use(cors({ + origin: 'http://localhost:5173' + , credentials: true + , optionsSuccessStatus: 200 + })) app.use(validator()) app.use(route) storage.on('timeout', function(id) { @@ -47,47 +53,47 @@ export default (function(options) { req.checkBody('url').notEmpty() }) .then(function() { - return download(req.body.url, { - dir: options.cacheDir - , jwt: req.internalJwt + return download(req.body.url, { + dir: options.cacheDir + , jwt: req.internalJwt + }) }) - }) .then(function(file) { - return { - id: storage.store(file) - , name: file.name - } - }) - .then(function(file) { - var plugin = req.params.plugin - res.status(201) - .json({ - success: true - , resource: { - date: new Date() - , plugin: plugin - , id: file.id + return { + id: storage.store(file) , name: file.name - , href: util.format('/s/%s/%s%s', plugin, file.id, file.name ? util.format('/%s', path.basename(file.name)) : '') } }) - }) + .then(function(file) { + var plugin = req.params.plugin + res.status(201) + .json({ + success: true + , resource: { + date: new Date() + , plugin: plugin + , id: file.id + , name: file.name + , href: util.format('/s/%s/%s%s', plugin, file.id, file.name ? util.format('/%s', path.basename(file.name)) : '') + } + }) + }) .catch(requtil.ValidationError, function(err) { - res.status(400) - .json({ - success: false - , error: 'ValidationError' - , validationErrors: err.errors + res.status(400) + .json({ + success: false + , error: 'ValidationError' + , validationErrors: err.errors + }) }) - }) .catch(function(err) { - log.error('Error storing resource', err.stack) - res.status(500) - .json({ - success: false - , error: 'ServerError' + log.error('Error storing resource', err.stack) + res.status(500) + .json({ + success: false + , error: 'ServerError' + }) }) - }) }) route.post('/s/upload/:plugin', function(req, res) { var form = new formidable.IncomingForm({ @@ -105,56 +111,56 @@ export default (function(options) { }) Promise.promisify(form.parse, form)(req) .spread(function(fields, files) { - return Object.keys(files).map(function(field) { - var file = files[field] - log.info('Uploaded "%s" to "%s"', file.name, file.path) - return { - field: field - , id: storage.store(file) - , name: file.name - , path: file.path - , isAab: file.isAab - } + return Object.keys(files).map(function(field) { + var file = files[field] + log.info('Uploaded "%s" to "%s"', file.name, file.path) + return { + field: field + , id: storage.store(file) + , name: file.name + , path: file.path + , isAab: file.isAab + } + }) }) - }) .then(function(storedFiles) { - return Promise.all(storedFiles.map(function(file) { - return bundletool({ - bundletoolPath: options.bundletoolPath - , keystore: options.keystore - , file: file - }) - })).then(function(storedFiles) { - res.status(201) - .json({ - success: true - , resources: (function() { - var mapped = Object.create(null) - storedFiles.forEach(function(file) { - var plugin = req.params.plugin - mapped[file.field] = { - date: new Date() - , plugin: plugin - , id: file.id - , name: file.name - , href: util.format('/s/%s/%s%s', plugin, file.id, file.name ? - util.format('/%s', path.basename(file.name)) : - '') - } + return Promise.all(storedFiles.map(function(file) { + return bundletool({ + bundletoolPath: options.bundletoolPath + , keystore: options.keystore + , file: file + }) + })).then(function(storedFiles) { + res.status(201) + .json({ + success: true + , resources: (function() { + var mapped = Object.create(null) + storedFiles.forEach(function(file) { + var plugin = req.params.plugin + mapped[file.field] = { + date: new Date() + , plugin: plugin + , id: file.id + , name: file.name + , href: util.format('/s/%s/%s%s', plugin, file.id, file.name ? + util.format('/%s', path.basename(file.name)) : + '') + } + }) + return mapped + })() }) - return mapped - })() }) }) - }) .catch(function(err) { - log.error('Error storing resource', err.stack) - res.status(500) - .json({ - success: false - , error: 'ServerError' + log.error('Error storing resource', err.stack) + res.status(500) + .json({ + success: false + , error: 'ServerError' + }) }) - }) }) route.get('/s/blob/:id/:name', function(req, res) { var file = storage.retrieve(req.params.id) diff --git a/lib/units/vnc-device/index.js b/lib/units/vnc-device/index.js index 2d78c1651d..eb8aa3539a 100755 --- a/lib/units/vnc-device/index.js +++ b/lib/units/vnc-device/index.js @@ -12,38 +12,38 @@ import plogger from './plugins/logger.js' export default function(options) { - // Show serial number in logs - logger.setGlobalIdentifier(options.serial) + // Show serial number in logs + logger.setGlobalIdentifier(options.serial) return syrup.serial() - .dependency(plogger) - .define(function(options) { - const log = logger.createLogger('vnc-device') - log.info('Preparing device options: ', options) + .dependency(plogger) + .define(function(options) { + const log = logger.createLogger('vnc-device') + log.info('Preparing device options: ', options) - return syrup.serial() - .dependency(heartbeat) - .dependency(solo) - .dependency(info) - .dependency(push) - .dependency(sub) - .dependency(group) - .dependency(stream) - .define(function(options, heartbeat, solo) { - if (process.send) { - process.send('ready') - } - try { - solo.poke() - } - catch(err) { - log.error('err :', err) - } + return syrup.serial() + .dependency(heartbeat) + .dependency(solo) + .dependency(info) + .dependency(push) + .dependency(sub) + .dependency(group) + .dependency(stream) + .define(function(options, heartbeat, solo) { + if (process.send) { + process.send('ready') + } + try { + solo.poke() + } + catch(err) { + log.error('err :', err) + } + }) + .consume(options) }) .consume(options) - }) - .consume(options) - .catch((err) => { - lifecycle.fatal(err) - }) + .catch((err) => { + lifecycle.fatal(err) + }) } diff --git a/lib/units/vnc-device/plugins/devicenotifier.js b/lib/units/vnc-device/plugins/devicenotifier.js index eedf3d71d6..6723f3e9cd 100644 --- a/lib/units/vnc-device/plugins/devicenotifier.js +++ b/lib/units/vnc-device/plugins/devicenotifier.js @@ -7,48 +7,48 @@ import push from '../../base-device/support/push.js' import group from './group.js' export default syrup.serial() - .dependency(push) - .dependency(group) - .define(function(options, push, group) { - const log = logger.createLogger('device:plugins:notifier') - const notifier = {} + .dependency(push) + .dependency(group) + .define(function(options, push, group) { + const log = logger.createLogger('device:plugins:notifier') + const notifier = {} - notifier.setDeviceTemporaryUnavailable = function(err) { - group.get() - .then((currentGroup) => { - push.send([ - currentGroup.group - , wireutil.envelope(new wire.TemporarilyUnavailableMessage( - options.serial - )) - ]) - }) - .catch(err => { - log.error('Cannot set device temporary unavailable', err) - }) - } + notifier.setDeviceTemporaryUnavailable = function(err) { + group.get() + .then((currentGroup) => { + push.send([ + currentGroup.group + , wireutil.envelope(new wire.TemporarilyUnavailableMessage( + options.serial + )) + ]) + }) + .catch(err => { + log.error('Cannot set device temporary unavailable', err) + }) + } - notifier.setDeviceAbsent = function(err) { - if (err.statusCode) { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceStatusMessage( - options.serial, - 1 - )) - ]) - } - else { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceAbsentMessage( - options.serial - )) - ]) - } + notifier.setDeviceAbsent = function(err) { + if (err.statusCode) { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceStatusMessage( + options.serial, + 1 + )) + ]) + } + else { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceAbsentMessage( + options.serial + )) + ]) + } - lifecycle.graceful(err) - } + lifecycle.graceful(err) + } - return notifier - }) + return notifier + }) diff --git a/lib/units/vnc-device/plugins/group.js b/lib/units/vnc-device/plugins/group.js index ddd9f45292..69cf9b8e98 100755 --- a/lib/units/vnc-device/plugins/group.js +++ b/lib/units/vnc-device/plugins/group.js @@ -6,8 +6,8 @@ import wire from '../../../wire/index.js' import wireutil from '../../../wire/util.js' import * as grouputil from '../../../util/grouputil.js' import lifecycle from '../../../util/lifecycle.js' -import dbapi from '../../../db/api.mjs' -import apiutil from '../../../util/apiutil.js' +import * as dbapi from '../../../db/api.js' +import * as apiutil from '../../../util/apiutil.js' import solo from './solo.js' import router from '../../base-device/support/router.js' import push from '../../base-device/support/push.js' @@ -15,164 +15,164 @@ import sub from '../../base-device/support/sub.js' import channels from '../../base-device/support/channels.js' export default syrup.serial() - .dependency(solo) - .dependency(router) - .dependency(push) - .dependency(sub) - .dependency(channels) - .define((options, solo, router, push, sub, channels) => { - const log = logger.createLogger('device:plugins:group') - let currentGroup = null - let plugin = new events.EventEmitter() - - plugin.get = Promise.method(() => { - if (!currentGroup) { - throw new grouputil.NoGroupError() - } - return currentGroup - }) - - plugin.join = (newGroup, timeout, usage) => { - return plugin.get() - .then(() => { - if (currentGroup.group !== newGroup.group) { - throw new grouputil.AlreadyGroupedError() - } - - log.info('Update timeout for ', apiutil.QUARTER_MINUTES) - channels.updateTimeout(currentGroup.group, apiutil.QUARTER_MINUTES) - - let newTimeout = channels.getTimeout(currentGroup.group) - - dbapi.enhanceStatusChangedAt(options.serial, newTimeout).then(() => { + .dependency(solo) + .dependency(router) + .dependency(push) + .dependency(sub) + .dependency(channels) + .define((options, solo, router, push, sub, channels) => { + const log = logger.createLogger('device:plugins:group') + let currentGroup = null + let plugin = new events.EventEmitter() + + plugin.get = Promise.method(() => { + if (!currentGroup) { + throw new grouputil.NoGroupError() + } return currentGroup - }) }) - .catch(grouputil.NoGroupError, () => { - currentGroup = newGroup - - log.important('Now owned by "%s"', currentGroup.email) - log.important('Device now in group "%s"', currentGroup.name) - log.info('Rent time is ' + timeout) - log.info('Subscribing to group channel "%s"', currentGroup.group) - - channels.register(currentGroup.group, { - timeout: timeout || options.groupTimeout - , alias: solo.channel - }) - - dbapi.enhanceStatusChangedAt(options.serial, timeout) - - sub.subscribe(currentGroup.group) - - push.send([ - wireutil.global - , wireutil.envelope(new wire.JoinGroupMessage( - options.serial - , currentGroup - , usage - )) - ]) - plugin.emit('join', currentGroup) - - return currentGroup + + plugin.join = (newGroup, timeout, usage) => { + return plugin.get() + .then(() => { + if (currentGroup.group !== newGroup.group) { + throw new grouputil.AlreadyGroupedError() + } + + log.info('Update timeout for ', apiutil.QUARTER_MINUTES) + channels.updateTimeout(currentGroup.group, apiutil.QUARTER_MINUTES) + + let newTimeout = channels.getTimeout(currentGroup.group) + + dbapi.enhanceStatusChangedAt(options.serial, newTimeout).then(() => { + return currentGroup + }) + }) + .catch(grouputil.NoGroupError, () => { + currentGroup = newGroup + + log.important('Now owned by "%s"', currentGroup.email) + log.important('Device now in group "%s"', currentGroup.name) + log.info('Rent time is ' + timeout) + log.info('Subscribing to group channel "%s"', currentGroup.group) + + channels.register(currentGroup.group, { + timeout: timeout || options.groupTimeout + , alias: solo.channel + }) + + dbapi.enhanceStatusChangedAt(options.serial, timeout) + + sub.subscribe(currentGroup.group) + + push.send([ + wireutil.global + , wireutil.envelope(new wire.JoinGroupMessage( + options.serial + , currentGroup + , usage + )) + ]) + plugin.emit('join', currentGroup) + + return currentGroup + }) + } + + plugin.keepalive = () => { + if (currentGroup) { + channels.keepalive(currentGroup.group) + } + } + + plugin.leave = (reason) => { + return plugin.get() + .then(group => { + log.important('No longer owned by "%s"', group.email) + log.info('Unsubscribing from group channel "%s"', group.group) + + channels.unregister(group.group) + sub.unsubscribe(group.group) + + push.send([ + wireutil.global + , wireutil.envelope(new wire.LeaveGroupMessage( + options.serial + , group + , reason + )) + ]) + + currentGroup = null + plugin.emit('leave', group) + + return group + }) + } + + router + .on(wire.GroupMessage, (channel, message) => { + let reply = wireutil.reply(options.serial) + Promise.method(() => { + return plugin.join(message.owner, message.timeout, message.usage) + })() + .then(() =>{ + push.send([ + channel + , reply.okay() + ]) + }) + .catch(grouputil.RequirementMismatchError, (err) => { + push.send([ + channel + , reply.fail(err.message) + ]) + }) + .catch(grouputil.AlreadyGroupedError, (err) => { + push.send([ + channel + , reply.fail(err.message) + ]) + }) + }) + .on(wire.AutoGroupMessage, (channel, message) => { + return plugin.join(message.owner, message.timeout, message.identifier) + .then(() => { + plugin.emit('autojoin', message.identifier, true) + }) + .catch(grouputil.AlreadyGroupedError, () => { + plugin.emit('autojoin', message.identifier, false) + }) + }) + .on(wire.UngroupMessage, (channel, message) => { + let reply = wireutil.reply(options.serial) + Promise.method(() => { + return plugin.leave('ungroup_request') + })() + .then(() => { + push.send([ + channel + , reply.okay() + ]) + }) + .catch(grouputil.NoGroupError, err => { + push.send([ + channel + , reply.fail(err.message) + ]) + }) + }) + + channels.on('timeout', channel => { + if (currentGroup && channel === currentGroup.group) { + plugin.leave('automatic_timeout') + } }) - } - - plugin.keepalive = () => { - if (currentGroup) { - channels.keepalive(currentGroup.group) - } - } - - plugin.leave = (reason) => { - return plugin.get() - .then(group => { - log.important('No longer owned by "%s"', group.email) - log.info('Unsubscribing from group channel "%s"', group.group) - - channels.unregister(group.group) - sub.unsubscribe(group.group) - - push.send([ - wireutil.global - , wireutil.envelope(new wire.LeaveGroupMessage( - options.serial - , group - , reason - )) - ]) - - currentGroup = null - plugin.emit('leave', group) - - return group + + lifecycle.observe(() => { + return plugin.leave('device_absent') + .catch(grouputil.NoGroupError, () => true) }) - } - - router - .on(wire.GroupMessage, (channel, message) => { - let reply = wireutil.reply(options.serial) - Promise.method(() => { - return plugin.join(message.owner, message.timeout, message.usage) - })() - .then(() =>{ - push.send([ - channel - , reply.okay() - ]) - }) - .catch(grouputil.RequirementMismatchError, (err) => { - push.send([ - channel - , reply.fail(err.message) - ]) - }) - .catch(grouputil.AlreadyGroupedError, (err) => { - push.send([ - channel - , reply.fail(err.message) - ]) - }) - }) - .on(wire.AutoGroupMessage, (channel, message) => { - return plugin.join(message.owner, message.timeout, message.identifier) - .then(() => { - plugin.emit('autojoin', message.identifier, true) - }) - .catch(grouputil.AlreadyGroupedError, () => { - plugin.emit('autojoin', message.identifier, false) - }) - }) - .on(wire.UngroupMessage, (channel, message) => { - let reply = wireutil.reply(options.serial) - Promise.method(() => { - return plugin.leave('ungroup_request') - })() - .then(() => { - push.send([ - channel - , reply.okay() - ]) - }) - .catch(grouputil.NoGroupError, err => { - push.send([ - channel - , reply.fail(err.message) - ]) - }) - }) - - channels.on('timeout', channel => { - if (currentGroup && channel === currentGroup.group) { - plugin.leave('automatic_timeout') - } - }) - lifecycle.observe(() => { - return plugin.leave('device_absent') - .catch(grouputil.NoGroupError, () => true) + return plugin }) - - return plugin - }) diff --git a/lib/units/vnc-device/plugins/heartbeat.js b/lib/units/vnc-device/plugins/heartbeat.js index 335ad23043..6bd3c77112 100755 --- a/lib/units/vnc-device/plugins/heartbeat.js +++ b/lib/units/vnc-device/plugins/heartbeat.js @@ -5,18 +5,18 @@ import wireutil from '../../../wire/util.js' import push from '../../base-device/support/push.js' export default syrup.serial() - .dependency(push) - .define((options, push) => { - function beat() { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceHeartbeatMessage( - options.serial - )) - ]) - } + .dependency(push) + .define((options, push) => { + function beat() { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceHeartbeatMessage( + options.serial + )) + ]) + } - let timer = setInterval(beat, options.heartbeatInterval) + let timer = setInterval(beat, options.heartbeatInterval) - lifecycle.observe(() => clearInterval(timer)) - }) + lifecycle.observe(() => clearInterval(timer)) + }) diff --git a/lib/units/vnc-device/plugins/info/index.js b/lib/units/vnc-device/plugins/info/index.js index 3377717c22..0470ec890d 100644 --- a/lib/units/vnc-device/plugins/info/index.js +++ b/lib/units/vnc-device/plugins/info/index.js @@ -6,56 +6,56 @@ import logger from '../../../../util/logger.js' import push from '../../../base-device/support/push.js' export default syrup.serial() - .dependency(push) - .define((options, push) => { - const log = logger.createLogger('vnc-device:info') + .dependency(push) + .define((options, push) => { + const log = logger.createLogger('vnc-device:info') - function manageDeviceInfo() { - return new Promise((resolve, reject) => { - log.info('device.name: ' + options.deviceName) + function manageDeviceInfo() { + return new Promise((resolve, reject) => { + log.info('device.name: ' + options.deviceName) - let solo = wireutil.makePrivateChannel() - let osName = options.deviceOs + let solo = wireutil.makePrivateChannel() + let osName = options.deviceOs - push.send([ - wireutil.global - , wireutil.envelope(new wire.InitializeIosDeviceState( - options.serial - , wireutil.toDeviceStatus('device') - , new wire.ProviderIosMessage( - solo, - options.provider, - options.screenWsUrlPattern || '' - ) - , new wire.IosDevicePorts( - options.screenPort, - options.mjpegPort - ) - , new wire.UpdateIosDevice( - options.serial, - options.deviceName, - osName, - osName - ) - )) - ]) + push.send([ + wireutil.global + , wireutil.envelope(new wire.InitializeIosDeviceState( + options.serial + , wireutil.toDeviceStatus('device') + , new wire.ProviderIosMessage( + solo, + options.provider, + options.screenWsUrlPattern || '' + ) + , new wire.IosDevicePorts( + options.screenPort, + options.mjpegPort + ) + , new wire.UpdateIosDevice( + options.serial, + options.deviceName, + osName, + osName + ) + )) + ]) - log.info('Storing device type value: ' + options.deviceType) - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceTypeMessage( - options.serial - , options.deviceType - )) - ]) + log.info('Storing device type value: ' + options.deviceType) + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceTypeMessage( + options.serial + , options.deviceType + )) + ]) - return resolve() - }) - .catch(err => { - return log.error(err, 'Failed to manage device info') - }) - } - return { - manageDeviceInfo - } - }) + return resolve() + }) + .catch(err => { + return log.error(err, 'Failed to manage device info') + }) + } + return { + manageDeviceInfo + } + }) diff --git a/lib/units/vnc-device/plugins/logger.js b/lib/units/vnc-device/plugins/logger.js index fe8a1b3020..4a7bc2ade7 100755 --- a/lib/units/vnc-device/plugins/logger.js +++ b/lib/units/vnc-device/plugins/logger.js @@ -6,23 +6,23 @@ import push from '../../base-device/support/push.js' export default syrup.serial() - .dependency(push) - .define((options, push) => { + .dependency(push) + .define((options, push) => { // Forward all logs - logger.on('entry', entry => { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceLogMessage( - options.serial - , entry.timestamp / 1000 - , entry.priority - , entry.tag - , entry.pid - , entry.message - , entry.identifier - )) - ]) - }) + logger.on('entry', entry => { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceLogMessage( + options.serial + , entry.timestamp / 1000 + , entry.priority + , entry.tag + , entry.pid + , entry.message + , entry.identifier + )) + ]) + }) - return logger - }) + return logger + }) diff --git a/lib/units/vnc-device/plugins/screen/stream.js b/lib/units/vnc-device/plugins/screen/stream.js index f2a4f13267..59607c0bcd 100755 --- a/lib/units/vnc-device/plugins/screen/stream.js +++ b/lib/units/vnc-device/plugins/screen/stream.js @@ -14,85 +14,85 @@ import push from '../../../base-device/support/push.js' import router from '../../../base-device/support/router.js' export default syrup.serial() - .dependency(solo) - .dependency(devicenotifier) - .dependency(push) - .dependency(router) - .define(function(options, solo, notifier, push, router) { - const wss = new WebSocket.Server({port: options.screenPort}) + .dependency(solo) + .dependency(devicenotifier) + .dependency(push) + .dependency(router) + .define(function(options, solo, notifier, push, router) { + const wss = new WebSocket.Server({port: options.screenPort}) - wss.on('connection', function connection(ws) { - let height, width - let lastClicked = {x: 0, y: 0} + wss.on('connection', function connection(ws) { + let height, width + let lastClicked = {x: 0, y: 0} - const r = rfb.createConnection({ - host: options.deviceUrl - , port: 5900 - , password: options.devicePassword - }) + const r = rfb.createConnection({ + host: options.deviceUrl + , port: 5900 + , password: options.devicePassword + }) - r.on('connect', function() { - logger.info('successfully connected and authorised') - logger.info('remote screen name: ' + r.title + ' width:' + r.width + ' height: ' + r.height) - width = r.width - height = r.height - push.send([ - wireutil.global - , wireutil.envelope(new wire.SizeIosDevice( - options.serial - , r.height - , r.width - , 1 - )) - ]) - }) + r.on('connect', function() { + logger.info('successfully connected and authorised') + logger.info('remote screen name: ' + r.title + ' width:' + r.width + ' height: ' + r.height) + width = r.width + height = r.height + push.send([ + wireutil.global + , wireutil.envelope(new wire.SizeIosDevice( + options.serial + , r.height + , r.width + , 1 + )) + ]) + }) - r.on('error', function(error) { - throw new Error(error) - }) + r.on('error', function(error) { + throw new Error(error) + }) - r.on('rect', function(rect) { - const image = jpeg.encode({data: rect.data, width: rect.width, height: rect.height}, options.screenJpegQuality) - ws.send(image.data) - r.requestUpdate(false, 0, 0, r.width, r.height) - }) + r.on('rect', function(rect) { + const image = jpeg.encode({data: rect.data, width: rect.width, height: rect.height}, options.screenJpegQuality) + ws.send(image.data) + r.requestUpdate(false, 0, 0, r.width, r.height) + }) - ws.on('error', function close() { - logger.info('Closing vnc connection because websocket closed') - r.end() - }) + ws.on('error', function close() { + logger.info('Closing vnc connection because websocket closed') + r.end() + }) - router - .on(wire.GestureStartMessage, function(channel, message) { - }) - .on(wire.GestureStopMessage, function(channel, message) { - }) - .on(wire.TouchDownIosMessage, function(channel, message) { - lastClicked.x = message.x * height - lastClicked.y = message.y * width - r.pointerEvent(message.x * height, message.y * width, 1) - }) - .on(wire.TouchMoveMessage, function(channel, message) { - }) - .on(wire.TouchUpMessage, function(channel, message) { - }) - .on(wire.TouchCommitMessage, function(channel, message) { - }) - .on(wire.TouchResetMessage, function(channel, message) { - }) - .on(wire.TypeMessage, function(channel, message) { - let keyCode = message.text.charCodeAt(0) - r.keyEvent(keyCode, 1) - r.keyEvent(keyCode, 0) - r.requestUpdate(false, 0, 0, r.width, r.height) - }) - .on(wire.KeyDownMessage, function(channel, message) { - r.keyEvent(keyNameToX11KeyCode(message.key), 1) - r.requestUpdate(false, 0, 0, r.width, r.height) - }) - .on(wire.KeyUpMessage, function(channel, message) { - r.keyEvent(keyNameToX11KeyCode(message.key), 0) - r.requestUpdate(false, 0, 0, r.width, r.height) + router + .on(wire.GestureStartMessage, function(channel, message) { + }) + .on(wire.GestureStopMessage, function(channel, message) { + }) + .on(wire.TouchDownMessage, function(channel, message) { + lastClicked.x = message.x * height + lastClicked.y = message.y * width + r.pointerEvent(message.x * height, message.y * width, 1) + }) + .on(wire.TouchMoveMessage, function(channel, message) { + }) + .on(wire.TouchUpMessage, function(channel, message) { + }) + .on(wire.TouchCommitMessage, function(channel, message) { + }) + .on(wire.TouchResetMessage, function(channel, message) { + }) + .on(wire.TypeMessage, function(channel, message) { + let keyCode = message.text.charCodeAt(0) + r.keyEvent(keyCode, 1) + r.keyEvent(keyCode, 0) + r.requestUpdate(false, 0, 0, r.width, r.height) + }) + .on(wire.KeyDownMessage, function(channel, message) { + r.keyEvent(keyNameToX11KeyCode(message.key), 1) + r.requestUpdate(false, 0, 0, r.width, r.height) + }) + .on(wire.KeyUpMessage, function(channel, message) { + r.keyEvent(keyNameToX11KeyCode(message.key), 0) + r.requestUpdate(false, 0, 0, r.width, r.height) + }) }) }) - }) diff --git a/lib/units/vnc-device/plugins/solo.js b/lib/units/vnc-device/plugins/solo.js index ee5728b747..8358aa4404 100755 --- a/lib/units/vnc-device/plugins/solo.js +++ b/lib/units/vnc-device/plugins/solo.js @@ -9,42 +9,42 @@ import push from '../../base-device/support/push.js' import info from '../plugins/info/index.js' export default syrup.serial() - .dependency(sub) - .dependency(push) - .dependency(info) - .define((options, sub, push, info) => { - const log = logger.createLogger('device:plugins:solo') + .dependency(sub) + .dependency(push) + .dependency(info) + .define((options, sub, push, info) => { + const log = logger.createLogger('device:plugins:solo') - // The channel should keep the same value between restarts, so that - // having the client side up to date all the time is not horribly painful. - let makeChannelId = () => { - let hash = crypto.createHash('sha1') - hash.update(options.serial) - return hash.digest('base64') - } + // The channel should keep the same value between restarts, so that + // having the client side up to date all the time is not horribly painful. + let makeChannelId = () => { + let hash = crypto.createHash('sha1') + hash.update(options.serial) + return hash.digest('base64') + } - let channel = makeChannelId() + let channel = makeChannelId() - log.info('Subscribing to permanent channel "%s"', channel) - sub.subscribe(channel) + log.info('Subscribing to permanent channel "%s"', channel) + sub.subscribe(channel) - return { - channel: channel - , poke: () => { - info.manageDeviceInfo() - .then(() => { - push.send([ - wireutil.global - , wireutil.envelope(new wire.DeviceReadyMessage( - options.serial - , channel - )) - ]) - }) - .catch(err => { - log.error('catch managerinfo', err) - lifecycle.fatal(err) - }) - } - } - }) + return { + channel: channel + , poke: () => { + info.manageDeviceInfo() + .then(() => { + push.send([ + wireutil.global + , wireutil.envelope(new wire.DeviceReadyMessage( + options.serial + , channel + )) + ]) + }) + .catch(err => { + log.error('catch managerinfo', err) + lifecycle.fatal(err) + }) + } + } + }) diff --git a/lib/units/vnc-device/plugins/util.js b/lib/units/vnc-device/plugins/util.js index 75bafce856..f2e8aaa063 100644 --- a/lib/units/vnc-device/plugins/util.js +++ b/lib/units/vnc-device/plugins/util.js @@ -1,42 +1,42 @@ export function keyNameToX11KeyCode(keyName) { - // Mapping stored in https://datatracker.ietf.org/doc/html/rfc6143#section-7.5.4 - const keyCodeMap = { - backspace: 0xff08 - , tab: 0xff09 - , enter: 0xff0d - , escape: 0xff1b - , insert: 0xff63 - , delete: 0xffff - , home: 0xff50 - , end: 0xff57 - , pageup: 0xff55 - , pagedown: 0xff56 - , dpad_left: 0xff51 - , dpad_up: 0xff52 - , dpad_right: 0xff53 - , dpad_down: 0xff54 - , f1: 0xffbe - , f2: 0xffbf - , f3: 0xffc0 - , f4: 0xffc1 - , f5: 0xffc2 - , f6: 0xffc3 - , f7: 0xffc4 - , f8: 0xffc5 - , f9: 0xffc6 - , f10: 0xffc7 - , f11: 0xffc8 - , f12: 0xffc9 - , shiftleft: 0xffe1 - , shiftright: 0xffe2 - , controlleft: 0xffe3 - , controlright: 0xffe4 - , metaleft: 0xffe7 - , metaright: 0xffe8 - , altleft: 0xffe9 - , altright: 0xffea - } + // Mapping stored in https://datatracker.ietf.org/doc/html/rfc6143#section-7.5.4 + const keyCodeMap = { + backspace: 0xff08 + , tab: 0xff09 + , enter: 0xff0d + , escape: 0xff1b + , insert: 0xff63 + , delete: 0xffff + , home: 0xff50 + , end: 0xff57 + , pageup: 0xff55 + , pagedown: 0xff56 + , dpad_left: 0xff51 + , dpad_up: 0xff52 + , dpad_right: 0xff53 + , dpad_down: 0xff54 + , f1: 0xffbe + , f2: 0xffbf + , f3: 0xffc0 + , f4: 0xffc1 + , f5: 0xffc2 + , f6: 0xffc3 + , f7: 0xffc4 + , f8: 0xffc5 + , f9: 0xffc6 + , f10: 0xffc7 + , f11: 0xffc8 + , f12: 0xffc9 + , shiftleft: 0xffe1 + , shiftright: 0xffe2 + , controlleft: 0xffe3 + , controlright: 0xffe4 + , metaleft: 0xffe7 + , metaright: 0xffe8 + , altleft: 0xffe9 + , altright: 0xffea + } - return keyCodeMap[keyName] || null + return keyCodeMap[keyName] || null } export default keyNameToX11KeyCode diff --git a/lib/units/websocket/index.js b/lib/units/websocket/index.js index a328c62960..917f58f9f3 100644 --- a/lib/units/websocket/index.js +++ b/lib/units/websocket/index.js @@ -14,7 +14,7 @@ import logger from '../../util/logger.js' import wire from '../../wire/index.js' import wireutil from '../../wire/util.js' import wirerouter from '../../wire/router.js' -import dbapi from '../../db/api.mjs' +import * as dbapi from '../../db/api.js' import datautil from '../../util/datautil.js' import srv from '../../util/srv.js' import lifecycle from '../../util/lifecycle.js' @@ -23,12 +23,13 @@ import cookieSession from './middleware/cookie-session.js' import ip from './middleware/remote-ip.js' import auth from './middleware/auth.js' import * as jwtutil from '../../util/jwtutil.js' -import apiutil from '../../util/apiutil.js' +import * as apiutil from '../../util/apiutil.js' import {Server} from 'socket.io' const request = Promise.promisifyAll(postmanRequest) export default (function(options) { var log = logger.createLogger('websocket') var server = http.createServer() + console.log(options) // eslint-disable-next-line camelcase const io_options = { serveClient: false @@ -50,9 +51,9 @@ export default (function(options) { }) }) .catch(function(err) { - log.fatal('Unable to connect to push endpoint', err) - lifecycle.fatal() - }) + log.fatal('Unable to connect to push endpoint', err) + lifecycle.fatal() + }) // Input var sub = zmqutil.socket('sub') Promise.map(options.endpoints.sub, function(endpoint) { @@ -83,7 +84,7 @@ export default (function(options) { return true } })) - io.use(auth) + io.use(auth({secret: options.secret})) io.on('connection', function(socket) { var req = socket.request var {user} = req @@ -109,348 +110,337 @@ export default (function(options) { let disconnectSocket var messageListener = wirerouter() .on(wire.UpdateAccessTokenMessage, function() { - socket.emit('user.keys.accessToken.updated') - }) + socket.emit('user.keys.accessToken.updated') + }) .on(wire.DeleteUserMessage, function() { - disconnectSocket(true) - }) + disconnectSocket(true) + }) .on(wire.DeviceChangeMessage, function(channel, message) { - if (user.groups.subscribed.indexOf(message.device.group.id) > -1) { - socket.emit('device.change', { - important: true - , data: { - serial: message.device.serial - , group: message.device.group - } - }) - } - if (user.groups.subscribed.indexOf(message.device.group.origin) > -1 || + if (user.groups.subscribed.indexOf(message.device.group.id) > -1) { + socket.emit('device.change', { + important: true + , data: { + serial: message.device.serial + , group: message.device.group + } + }) + } + if (user.groups.subscribed.indexOf(message.device.group.origin) > -1 || user.groups.subscribed.indexOf(message.oldOriginGroupId) > -1) { - socket.emit('user.settings.devices.' + message.action, message) - } - }) + socket.emit('user.settings.devices.' + message.action, message) + } + }) .on(wire.UserChangeMessage, function(channel, message) { - Promise.map(message.targets, function(target) { - socket.emit('user.' + target + '.users.' + message.action, message) + Promise.map(message.targets, function(target) { + socket.emit('user.' + target + '.users.' + message.action, message) + }) }) - }) .on(wire.GroupChangeMessage, function(channel, message) { - if (user.privilege === 'admin' || + if (user.privilege === 'admin' || user.email === message.group.owner.email || !apiutil.isOriginGroup(message.group.class) && (message.action === 'deleted' || message.action === 'updated' && (message.isChangedDates || message.isChangedClass || message.devices.length))) { - socket.emit('user.settings.groups.' + message.action, message) - } - if (message.subscribers.indexOf(user.email) > -1) { - socket.emit('user.view.groups.' + message.action, message) - } - }) - .on(wire.DeviceGroupChangeMessage, function(channel, message) { - if (user.groups.subscribed.indexOf(message.id) > -1) { - if (user.groups.subscribed.indexOf(message.group.id) > -1) { - socket.emit('device.updateGroupDevice', { - important: true - , data: { - serial: message.serial - , group: message.group - } - }) + socket.emit('user.settings.groups.' + message.action, message) } - else { - socket.emit('device.removeGroupDevices', {important: true, devices: [message.serial]}) + if (message.subscribers.indexOf(user.email) > -1) { + socket.emit('user.view.groups.' + message.action, message) } - } - else if (user.groups.subscribed.indexOf(message.group.id) > -1) { - socket.emit('device.addGroupDevices', {important: true, devices: [message.serial]}) - } - }) - .on(wire.GroupUserChangeMessage, function(channel, message) { - if (message.users.indexOf(user.email) > -1) { - if (message.isAdded) { - user.groups.subscribed = _.union(user.groups.subscribed, [message.id]) - if (message.devices.length) { - socket.emit('device.addGroupDevices', {important: true, devices: message.devices}) + }) + .on(wire.DeviceGroupChangeMessage, function(channel, message) { + if (user.groups.subscribed.indexOf(message.id) > -1) { + if (user.groups.subscribed.indexOf(message.group.id) > -1) { + socket.emit('device.updateGroupDevice', { + important: true + , data: { + serial: message.serial + , group: message.group + } + }) } - } - else { - if (message.devices.length) { - socket.emit('device.removeGroupDevices', {important: true, devices: message.devices}) + else { + socket.emit('device.removeGroupDevices', {important: true, devices: [message.serial]}) } - if (message.isDeletedLater) { - setTimeout(function() { - user.groups.subscribed = _.without(user.groups.subscribed, message.id) - }, 5000) + } + else if (user.groups.subscribed.indexOf(message.group.id) > -1) { + socket.emit('device.addGroupDevices', {important: true, devices: [message.serial]}) + } + }) + .on(wire.GroupUserChangeMessage, function(channel, message) { + if (message.users.indexOf(user.email) > -1) { + if (message.isAdded) { + user.groups.subscribed = _.union(user.groups.subscribed, [message.id]) + if (message.devices.length) { + socket.emit('device.addGroupDevices', {important: true, devices: message.devices}) + } } else { - user.groups.subscribed = _.without(user.groups.subscribed, message.id) + if (message.devices.length) { + socket.emit('device.removeGroupDevices', {important: true, devices: message.devices}) + } + if (message.isDeletedLater) { + setTimeout(function() { + user.groups.subscribed = _.without(user.groups.subscribed, message.id) + }, 5000) + } + else { + user.groups.subscribed = _.without(user.groups.subscribed, message.id) + } } } - } - }) + }) .on(wire.DeviceLogMessage, function(channel, message) { - io.emit('logcat.log', message) - }) + io.emit('logcat.log', message) + }) .on(wire.DeviceIntroductionMessage, function(channel, message) { - if (message && message.group && user.groups.subscribed.indexOf(message.group.id) > -1) { - io.emit('device.add', { + if (message && message.group && user.groups.subscribed.indexOf(message.group.id) > -1) { + io.emit('device.add', { + important: true + , data: { + serial: message.serial + , present: true + , provider: message.provider + , owner: null + , status: message.status + , ready: false + , reverseForwards: [] + , group: message.group + } + }) + } + }) + .on(wire.DeviceReadyMessage, function(channel, message) { + io.emit('device.change', { important: true , data: { serial: message.serial - , present: true - , provider: message.provider - , owner: null - , status: message.status - , ready: false - , reverseForwards: [] - , group: message.group + , channel: message.channel + , owner: null // @todo Get rid of need to reset this here. + , ready: true + , reverseForwards: [] // @todo Get rid of need to reset this here. } }) - } - }) - .on(wire.DeviceReadyMessage, function(channel, message) { - io.emit('device.change', { - important: true - , data: { - serial: message.serial - , channel: message.channel - , owner: null // @todo Get rid of need to reset this here. - , ready: true - , reverseForwards: [] // @todo Get rid of need to reset this here. - } }) - }) .on(wire.DevicePresentMessage, function(channel, message) { - io.emit('device.change', { - important: true - , data: { - serial: message.serial - , present: true - } + io.emit('device.change', { + important: true + , data: { + serial: message.serial + , present: true + } + }) }) - }) .on(wire.DeviceAbsentMessage, function(channel, message) { - io.emit('device.remove', { - important: true - , data: { - serial: message.serial - , present: false - , likelyLeaveReason: 'device_absent' - } - }) - }) - .on(wire.InstalledApplications, function(channel, message, data) { - socket.emit('device.applications', { - important: true - , data: { - serial: message.serial - , applications: message.applications - } - }) - }) - // @TODO refactore JoimGroupMessage route - .on(wire.JoinGroupMessage, function(channel, message) { - dbapi.getInstalledApplications({serial: message.serial}) - .then(applications => { - socket.emit(`device.application-${message.serial}`, { - applications: applications - }) - socket.emit('device.change', { + io.emit('device.remove', { important: true - , data: datautil.applyOwner({ + , data: { serial: message.serial - , owner: message.owner - , likelyLeaveReason: 'owner_change' - , usage: message.usage - , applications: applications - }, user) + , present: false + , likelyLeaveReason: 'device_absent' + } }) }) - .catch(err => { - socket.emit('device.change', { + .on(wire.InstalledApplications, function(channel, message, data) { + socket.emit('device.applications', { important: true - , data: datautil.applyOwner({ + , data: { serial: message.serial - , owner: message.owner - , likelyLeaveReason: 'owner_change' - , usage: message.usage - }, user) + , applications: message.applications + } }) }) - }) + // @TODO refactore JoimGroupMessage route + .on(wire.JoinGroupMessage, function(channel, message) { + dbapi.getInstalledApplications({serial: message.serial}) + .then(applications => { + socket.emit(`device.application-${message.serial}`, { + applications: applications + }) + socket.emit('device.change', { + important: true + , data: datautil.applyOwner({ + serial: message.serial + , owner: message.owner + , likelyLeaveReason: 'owner_change' + , usage: message.usage + , applications: applications + }, user) + }) + }) + .catch(err => { + socket.emit('device.change', { + important: true + , data: datautil.applyOwner({ + serial: message.serial + , owner: message.owner + , likelyLeaveReason: 'owner_change' + , usage: message.usage + }, user) + }) + }) + }) .on(wire.JoinGroupByAdbFingerprintMessage, function(channel, message) { - socket.emit('user.keys.adb.confirm', { - title: message.comment - , fingerprint: message.fingerprint + socket.emit('user.keys.adb.confirm', { + title: message.comment + , fingerprint: message.fingerprint + }) }) - }) .on(wire.LeaveGroupMessage, function(channel, message) { - io.emit('device.change', { - important: true - , data: datautil.applyOwner({ - serial: message.serial - , owner: null - , likelyLeaveReason: message.reason - }, user) - }) - }) - .on(wire.DeviceOnInstAppMessage, function(channel, message) { - dbapi.getInstalledApplications({serial: message.serial}) - .then(applications => { - socket.emit(`device.application-${message.serial}`, { - applications: applications - }) - socket.emit('device.change', { + io.emit('device.change', { important: true - , data: { + , data: datautil.applyOwner({ serial: message.serial - , applications: applications - }, + , owner: null + , likelyLeaveReason: message.reason + }, user) }) }) - .catch(err => { - socket.emit('device.change', { - importatnt: true, - }) + .on(wire.DeviceOnInstAppMessage, function(channel, message) { + dbapi.getInstalledApplications({serial: message.serial}) + .then(applications => { + socket.emit(`device.application-${message.serial}`, { + applications: applications + }) + socket.emit('device.change', { + important: true + , data: { + serial: message.serial + , applications: applications + }, + }) + }) + .catch(err => { + socket.emit('device.change', { + importatnt: true, + }) + }) }) - }) .on(wire.DeviceStatusMessage, function(channel, message) { - message.likelyLeaveReason = 'status_change' - io.emit('device.change', { - important: true - , data: message + message.likelyLeaveReason = 'status_change' + io.emit('device.change', { + important: true + , data: message + }) }) - }) .on(wire.DeviceIdentityMessage, function(channel, message) { - datautil.applyData(message) - io.emit('device.change', { - important: true - , data: message + datautil.applyData(message) + io.emit('device.change', { + important: true + , data: message + }) }) - }) .on(wire.TransactionProgressMessage, function(channel, message) { - socket.emit('tx.progress', channel.toString(), message) - }) + socket.emit('tx.progress', channel.toString(), message) + }) .on(wire.TransactionDoneMessage, function(channel, message) { - socket.emit('tx.done', channel.toString(), message) - }) + socket.emit('tx.done', channel.toString(), message) + }) .on(wire.TransactionTreeMessage, function(channel, message) { - socket.emit('tx.tree', channel.toString(), message) - }) + socket.emit('tx.tree', channel.toString(), message) + }) .on(wire.DeviceLogcatEntryMessage, function(channel, message) { - socket.emit('logcat.entry', message) - }) + socket.emit('logcat.entry', message) + }) .on(wire.AirplaneModeEvent, function(channel, message) { - io.emit('device.change', { - important: true - , data: { - serial: message.serial - , airplaneMode: message.enabled - } + io.emit('device.change', { + important: true + , data: { + serial: message.serial + , airplaneMode: message.enabled + } + }) }) - }) .on(wire.BatteryEvent, function(channel, message) { - var {serial} = message - delete message.serial - io.emit('device.change', { - important: false - , data: { - serial: serial - , battery: message - } + var {serial} = message + delete message.serial + io.emit('device.change', { + important: false + , data: { + serial: serial + , battery: message + } + }) }) - }) .on(wire.GetServicesAvailabilityMessage, function(channel, message) { - let serial = message.serial - delete message.serial - io.emit('device.change', { - important: true - , data: { - serial: serial - , service: message - } + let serial = message.serial + delete message.serial + io.emit('device.change', { + important: true + , data: { + serial: serial + , service: message + } + }) }) - }) .on(wire.DeviceBrowserMessage, function(channel, message) { - var {serial} = message - delete message.serial - io.emit('device.change', { - important: true - , data: datautil.applyBrowsers({ - serial: serial - , browser: message + var {serial} = message + delete message.serial + io.emit('device.change', { + important: true + , data: datautil.applyBrowsers({ + serial: serial + , browser: message + }) }) }) - }) .on(wire.ConnectivityEvent, function(channel, message) { - var {serial} = message - delete message.serial - io.emit('device.change', { - important: false - , data: { - serial: serial - , network: message - } + var {serial} = message + delete message.serial + io.emit('device.change', { + important: false + , data: { + serial: serial + , network: message + } + }) }) - }) .on(wire.PhoneStateEvent, function(channel, message) { - var {serial} = message - delete message.serial - io.emit('device.change', { - important: false - , data: { - serial: serial - , network: message - } - }) - }) - .on(wire.RotationEvent, function(channel, message) { - socket.emit('device.change', { - important: false - , data: { - serial: message.serial - , display: { - rotation: message.rotation + var {serial} = message + delete message.serial + io.emit('device.change', { + important: false + , data: { + serial: serial + , network: message } - } + }) }) - }) - .on(wire.RotationIosEvent, function(channel, message) { - socket.emit('device.change', { - important: false - , data: { - serial: message.serial - , display: { - rotation: message.rotation + .on(wire.RotationEvent, function(channel, message) { + socket.emit('device.change', { + important: false + , data: { + serial: message.serial + , display: { + rotation: message.rotation + } } - } + }) }) - }) .on(wire.ReverseForwardsEvent, function(channel, message) { - socket.emit('device.change', { - important: false - , data: { - serial: message.serial - , reverseForwards: message.forwards - } + socket.emit('device.change', { + important: false + , data: { + serial: message.serial + , reverseForwards: message.forwards + } + }) }) - }) .on(wire.TemporarilyUnavailableMessage, function(channel, message) { - socket.emit('temporarily-unavailable', { - data: { - removeConnectUrl: message.removeConnectUrl - } + socket.emit('temporarily-unavailable', { + data: { + removeConnectUrl: message.removeConnectUrl + } + }) }) - }) .on(wire.UpdateRemoteConnectUrl, function(channel, message) { - socket.emit('device.change', { - important: true - , data: { - serial: message.serial - } + socket.emit('device.change', { + important: true + , data: { + serial: message.serial + } + }) }) - }) .handler() channelRouter.on(wireutil.global, messageListener) // User's private group @@ -462,15 +452,13 @@ export default (function(options) { // // Device note .on('device.note', function(data) { - return dbapi - .setDeviceNote(data.serial, data.note) - .then(function() { - return dbapi.loadDevice(user.groups.subscribed, data.serial) - }) - .then(function(cursor) { - if (cursor) { - cursor.next(function(err, device) { - if (!err) { + return dbapi + .setDeviceNote(data.serial, data.note) + .then(function() { + return dbapi.loadDevice(user.groups.subscribed, data.serial) + }) + .then(function(device) { + if (device) { io.emit('device.change', { important: true , data: { @@ -480,149 +468,147 @@ export default (function(options) { }) } }) - } }) - }) // Client specific messages // // Settings .on('user.settings.update', function(data) { - if (data.alertMessage === undefined) { - dbapi.updateUserSettings(user.email, data) - } - else { - dbapi.updateUserSettings(apiutil.STF_ADMIN_EMAIL, data) - } - }) + if (data.alertMessage === undefined) { + dbapi.updateUserSettings(user.email, data) + } + else { + dbapi.updateUserSettings(apiutil.STF_ADMIN_EMAIL, data) + } + }) .on('user.settings.reset', function() { - dbapi.resetUserSettings(user.email) - }) + dbapi.resetUserSettings(user.email) + }) .on('user.keys.accessToken.generate', function(data) { - var jwt = jwtutil.encode({ - payload: { - email: user.email - , name: user.name - } - , secret: options.secret - }) - var tokenId = util - .format('%s-%s', uuid.v4(), uuid.v4()) - .replace(/-/g, '') - var {title} = data - return dbapi - .saveUserAccessToken(user.email, { - title: title - , id: tokenId - , jwt: jwt - }) - .then(function() { - socket.emit('user.keys.accessToken.generated', { - title: title - , tokenId: tokenId + var jwt = jwtutil.encode({ + payload: { + email: user.email + , name: user.name + } + , secret: options.secret }) + var tokenId = util + .format('%s-%s', uuid.v4(), uuid.v4()) + .replace(/-/g, '') + var {title} = data + return dbapi + .saveUserAccessToken(user.email, { + title: title + , id: tokenId + , jwt: jwt + }) + .then(function() { + socket.emit('user.keys.accessToken.generated', { + title: title + , tokenId: tokenId + }) + }) }) - }) .on('user.keys.accessToken.remove', function(data) { - return dbapi - .removeUserAccessToken(user.email, data.title) - .then(function() { - socket.emit('user.keys.accessToken.updated') + return dbapi + .removeUserAccessToken(user.email, data.title) + .then(function() { + socket.emit('user.keys.accessToken.updated') + }) }) - }) .on('user.keys.adb.add', function(data) { - return adbkit.Adb.util.parsePublicKey(data.key) - .then(function(key) { - return dbapi.lookupUsersByAdbKey(key.fingerprint) + return adbkit.Adb.util.parsePublicKey(data.key) + .then(function(key) { + return dbapi.lookupUsersByAdbKey(key.fingerprint) + .then(function(keys) { + return keys + }) + .then(function(users) { + if (users.length) { + throw new dbapi.DuplicateSecondaryIndexError() + } + else { + return dbapi.insertUserAdbKey(user.email, { + title: data.title + , fingerprint: key.fingerprint + }) + } + }) + .then(function() { + socket.emit('user.keys.adb.added', { + title: data.title + , fingerprint: key.fingerprint + }) + }) + }) + .then(function() { + push.send([ + wireutil.global + , wireutil.envelope(new wire.AdbKeysUpdatedMessage()) + ]) + }) + .catch(dbapi.DuplicateSecondaryIndexError, function(err) { + socket.emit('user.keys.adb.error', { + message: 'Someone already added this key' + }) + }) + .catch(Error, function(err) { + socket.emit('user.keys.adb.error', { + message: err.message + }) + }) + }) + .on('user.keys.adb.accept', function(data) { + return dbapi.lookupUsersByAdbKey(data.fingerprint) .then(function(keys) { - return keys - }) + return keys + }) .then(function(users) { - if (users.length) { - throw new dbapi.DuplicateSecondaryIndexError() - } - else { - return dbapi.insertUserAdbKey(user.email, { + if (users.length) { + throw new dbapi.DuplicateSecondaryIndexError() + } + else { + return dbapi.insertUserAdbKey(user.email, { + title: data.title + , fingerprint: data.fingerprint + }) + } + }) + .then(function() { + socket.emit('user.keys.adb.added', { title: data.title - , fingerprint: key.fingerprint + , fingerprint: data.fingerprint }) - } - }) + }) .then(function() { - socket.emit('user.keys.adb.added', { - title: data.title - , fingerprint: key.fingerprint + push.send([ + user.group + , wireutil.envelope(new wire.AdbKeysUpdatedMessage()) + ]) }) - }) - }) - .then(function() { - push.send([ - wireutil.global - , wireutil.envelope(new wire.AdbKeysUpdatedMessage()) - ]) - }) - .catch(dbapi.DuplicateSecondaryIndexError, function(err) { - socket.emit('user.keys.adb.error', { - message: 'Someone already added this key' - }) - }) - .catch(Error, function(err) { - socket.emit('user.keys.adb.error', { - message: err.message - }) - }) - }) - .on('user.keys.adb.accept', function(data) { - return dbapi.lookupUsersByAdbKey(data.fingerprint) - .then(function(keys) { - return keys - }) - .then(function(users) { - if (users.length) { - throw new dbapi.DuplicateSecondaryIndexError() - } - else { - return dbapi.insertUserAdbKey(user.email, { - title: data.title - , fingerprint: data.fingerprint + .catch(dbapi.DuplicateSecondaryIndexError, function() { + // No-op }) - } - }) - .then(function() { - socket.emit('user.keys.adb.added', { - title: data.title - , fingerprint: data.fingerprint - }) - }) - .then(function() { - push.send([ - user.group - , wireutil.envelope(new wire.AdbKeysUpdatedMessage()) - ]) - }) - .catch(dbapi.DuplicateSecondaryIndexError, function() { - // No-op }) - }) .on('user.keys.adb.remove', function(data) { - return dbapi - .deleteUserAdbKey(user.email, data.fingerprint) - .then(function() { - socket.emit('user.keys.adb.removed', data) + return dbapi + .deleteUserAdbKey(user.email, data.fingerprint) + .then(function() { + socket.emit('user.keys.adb.removed', data) + }) }) - }) .on('shell.settings.execute', function(data) { - let command = data.command - dbapi.loadDevices().then(devices => { - devices.forEach(device => { - push.send([ - device.channel - , wireutil.envelope(new wire.ShellCommandMessage({ - command: command - , timeout: 10000 - })) - ]) + let command = data.command + dbapi.loadDevices().then(devices => { + devices.forEach(device => { + push.send([ + device.channel + , wireutil.envelope(new wire.ShellCommandMessage({ + command: command + , timeout: 10000 + })) + ]) + }) }) - }) // TODO: поддержать обратный ответ // joinChannel(responseChannel) // push.send([ @@ -632,488 +618,476 @@ export default (function(options) { // , new wire.ShellCommandMessage(data) // ) // ]) - }) + }) // Touch events .on('input.touchDown', function(channel, data) { - push.send([ - channel - , wireutil.envelope(new wire.TouchDownMessage(data.seq, data.contact, data.x, data.y, data.pressure)) - ]) - }) - .on('input.touchMove', function(channel, data) { - try { push.send([ channel - , wireutil.envelope(new wire.TouchMoveMessage(data.seq, data.contact, data.x, data.y, data.pressure)) + , wireutil.envelope(new wire.TouchDownMessage(data.seq, data.contact, data.x, data.y, data.pressure)) ]) - } - catch (err) { + }) + .on('input.touchMove', function(channel, data) { + try { + push.send([ + channel + , wireutil.envelope(new wire.TouchMoveMessage(data.seq, data.contact, data.x, data.y, data.pressure)) + ]) + } + catch (err) { // workaround for https://github.com/openstf/stf/issues/1180 - log.error('input.touchMove had an error', err.stack) - } - }) - .on('input.touchDownIos', function(channel, data) { - // TODO prevent sendint undefined touch down message - try { + log.error('input.touchMove had an error', err.stack) + } + }) + .on('input.touchMoveIos', function(channel, data) { + data.duration = 0.042 push.send([ channel - , wireutil.envelope(new wire.TouchDownIosMessage(data.x, data.y)) + , wireutil.envelope(new wire.TouchMoveIosMessage(data.toX, data.toY, data.fromX, data.fromY, data.duration)) ]) - } - catch (e) { - log.error(e) - } - }) - .on('input.touchMoveIos', function(channel, data) { - data.duration = 0.042 - push.send([ - channel - , wireutil.envelope(new wire.TouchMoveIosMessage(data.toX, data.toY, data.fromX, data.fromY, data.duration)) - ]) - }) + }) .on('tapDeviceTreeElement', function(channel, data) { - push.send([ - channel - , wireutil.envelope(new wire.TapDeviceTreeElement(data.label)) - ]) - }) + push.send([ + channel + , wireutil.envelope(new wire.TapDeviceTreeElement(data.label)) + ]) + }) .on('input.touchUp', function(channel, data) { - push.send([ - channel - , wireutil.envelope(new wire.TouchUpMessage(data.seq, data.contact)) - ]) - }) + push.send([ + channel + , wireutil.envelope(new wire.TouchUpMessage(data.seq, data.contact)) + ]) + }) .on('input.touchCommit', function(channel, data) { - push.send([ - channel - , wireutil.envelope(new wire.TouchCommitMessage(data.seq)) - ]) - }) + push.send([ + channel + , wireutil.envelope(new wire.TouchCommitMessage(data.seq)) + ]) + }) .on('input.touchReset', function(channel, data) { - push.send([ - channel - , wireutil.envelope(new wire.TouchResetMessage(data.seq)) - ]) - }) + push.send([ + channel + , wireutil.envelope(new wire.TouchResetMessage(data.seq)) + ]) + }) .on('input.gestureStart', function(channel, data) { - push.send([ - channel - , wireutil.envelope(new wire.GestureStartMessage(data.seq)) - ]) - }) + push.send([ + channel + , wireutil.envelope(new wire.GestureStartMessage(data.seq)) + ]) + }) .on('input.gestureStop', function(channel, data) { - push.send([ - channel - , wireutil.envelope(new wire.GestureStopMessage(data.seq)) - ]) - }) + push.send([ + channel + , wireutil.envelope(new wire.GestureStopMessage(data.seq)) + ]) + }) // Key events .on('input.keyDown', createKeyHandler(wire.KeyDownMessage)) .on('input.keyUp', createKeyHandler(wire.KeyUpMessage)) .on('input.keyPress', createKeyHandler(wire.KeyPressMessage)) .on('input.type', function(channel, data) { - push.send([ - channel - , wireutil.envelope(new wire.TypeMessage(data.text)) - ]) - }) + push.send([ + channel + , wireutil.envelope(new wire.TypeMessage(data.text)) + ]) + }) .on('display.rotate', function(channel, data) { - push.send([ - channel - , wireutil.envelope(new wire.RotateMessage(data.rotation)) - ]) - }) + push.send([ + channel + , wireutil.envelope(new wire.RotateMessage(data.rotation)) + ]) + }) .on('quality.change', function(channel, data) { - push.send([ - channel - , wireutil.envelope(new wire.ChangeQualityMessage(data.quality)) - ]) - }) + push.send([ + channel + , wireutil.envelope(new wire.ChangeQualityMessage(data.quality)) + ]) + }) // Transactions .on('clipboard.paste', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.PasteMessage(data.text)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.PasteMessage(data.text)) + ]) + }) .on('clipboard.copy', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.CopyMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.CopyMessage()) + ]) + }) .on('clipboard.copyIos', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.CopyMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.CopyMessage()) + ]) + }) .on('device.identify', function(channel, responseChannel) { - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.PhysicalIdentifyMessage()) - ]) - }) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.PhysicalIdentifyMessage()) + ]) + }) .on('device.reboot', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.RebootMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.RebootMessage()) + ]) + }) .on('device.rebootIos', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.RebootMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.RebootMessage()) + ]) + }) .on('account.check', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.AccountCheckMessage(data)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.AccountCheckMessage(data)) + ]) + }) .on('account.remove', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.AccountRemoveMessage(data)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.AccountRemoveMessage(data)) + ]) + }) .on('account.addmenu', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.AccountAddMenuMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.AccountAddMenuMessage()) + ]) + }) .on('account.add', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.AccountAddMessage(data.user, data.password)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.AccountAddMessage(data.user, data.password)) + ]) + }) .on('account.get', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.AccountGetMessage(data)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.AccountGetMessage(data)) + ]) + }) .on('sd.status', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.SdStatusMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.SdStatusMessage()) + ]) + }) .on('ringer.set', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.RingerSetMessage(data.mode)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.RingerSetMessage(data.mode)) + ]) + }) .on('ringer.get', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.RingerGetMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.RingerGetMessage()) + ]) + }) .on('wifi.set', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.WifiSetEnabledMessage(data.enabled)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.WifiSetEnabledMessage(data.enabled)) + ]) + }) .on('wifi.get', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.WifiGetStatusMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.WifiGetStatusMessage()) + ]) + }) .on('bluetooth.set', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.BluetoothSetEnabledMessage(data.enabled)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.BluetoothSetEnabledMessage(data.enabled)) + ]) + }) .on('bluetooth.get', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.BluetoothGetStatusMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.BluetoothGetStatusMessage()) + ]) + }) .on('bluetooth.cleanBonds', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.BluetoothCleanBondedMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.BluetoothCleanBondedMessage()) + ]) + }) .on('group.invite', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.GroupMessage(new wire.OwnerMessage(user.email, user.name, user.group), data.timeout || null, wireutil.toDeviceRequirements(data.requirements))) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.GroupMessage(new wire.OwnerMessage(user.email, user.name, user.group), data.timeout || null, wireutil.toDeviceRequirements(data.requirements))) + ]) + }) .on('group.kick', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.UngroupMessage(wireutil.toDeviceRequirements(data.requirements))) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.UngroupMessage(wireutil.toDeviceRequirements(data.requirements))) + ]) + }) .on('getTreeElementsIos', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.GetIosTreeElements()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.GetIosTreeElements()) + ]) + }) .on('tx.cleanup', function(channel) { - leaveChannel(channel) - }) + leaveChannel(channel) + }) .on('tx.punch', function(channel) { - joinChannel(channel) - socket.emit('tx.punch', channel) - }) + joinChannel(channel) + socket.emit('tx.punch', channel) + }) .on('shell.command', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.ShellCommandMessage(data)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.ShellCommandMessage(data)) + ]) + }) .on('shell.keepalive', function(channel, data) { - push.send([ - channel - , wireutil.envelope(new wire.ShellKeepAliveMessage(data)) - ]) - }) + push.send([ + channel + , wireutil.envelope(new wire.ShellKeepAliveMessage(data)) + ]) + }) .on('device.install', function(channel, responseChannel, data) { - const installFlags = ['-r'] - const isApi = false - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.InstallMessage(data.href, data.launch === true, isApi, JSON.stringify(data.manifest), installFlags)) - ]) - }) + const installFlags = ['-r'] + const isApi = false + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.InstallMessage(data.href, data.launch === true, isApi, JSON.stringify(data.manifest), installFlags)) + ]) + }) .on('device.installIos', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.InstallMessage(data.href, data.launch === true, JSON.stringify(data.manifest))) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.InstallMessage(data.href, data.launch === true, JSON.stringify(data.manifest))) + ]) + }) .on('device.uninstall', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.UninstallMessage(data)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.UninstallMessage(data)) + ]) + }) .on('device.uninstallIos', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.envelope(new wire.UninstallIosMessage(data.packageName)) - ]) - }) - .on('storage.upload', function(channel, responseChannel, data) { - joinChannel(responseChannel) - request - .postAsync({ - url: util.format('%sapi/v1/resources?channel=%s', options.storageUrl, responseChannel) - , json: true - , body: { - url: data.url - } + joinChannel(responseChannel) + push.send([ + channel + , wireutil.envelope(new wire.UninstallIosMessage(data.packageName)) + ]) }) - .catch(function(err) { - log.error('Storage upload had an error', err.stack) - leaveChannel(responseChannel) - socket.emit('tx.cancel', responseChannel, { - success: false - , data: 'fail_upload' - }) + .on('storage.upload', function(channel, responseChannel, data) { + joinChannel(responseChannel) + request + .postAsync({ + url: util.format('%sapi/v1/resources?channel=%s', options.storageUrl, responseChannel) + , json: true + , body: { + url: data.url + } + }) + .catch(function(err) { + log.error('Storage upload had an error', err.stack) + leaveChannel(responseChannel) + socket.emit('tx.cancel', responseChannel, { + success: false + , data: 'fail_upload' + }) + }) }) - }) .on('forward.test', function(channel, responseChannel, data) { - joinChannel(responseChannel) - if (!data.targetHost || data.targetHost === 'localhost') { - data.targetHost = user.ip - } - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.ForwardTestMessage(data)) - ]) - }) + joinChannel(responseChannel) + if (!data.targetHost || data.targetHost === 'localhost') { + data.targetHost = user.ip + } + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.ForwardTestMessage(data)) + ]) + }) .on('forward.create', function(channel, responseChannel, data) { - if (!data.targetHost || data.targetHost === 'localhost') { - data.targetHost = user.ip - } - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.ForwardCreateMessage(data)) - ]) - }) + if (!data.targetHost || data.targetHost === 'localhost') { + data.targetHost = user.ip + } + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.ForwardCreateMessage(data)) + ]) + }) .on('forward.remove', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.ForwardRemoveMessage(data)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.ForwardRemoveMessage(data)) + ]) + }) .on('logcat.start', function(channel, responseChannel, data) { // #455 and #459 - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.LogcatStartMessage({filters: {tag: '*', priority: 2}})) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.LogcatStartMessage({filters: {tag: '*', priority: 2}})) + ]) + }) .on('logcat.startIos', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.LogcatStartMessage({filters: {tag: data.filters[0], priority: 1}})) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.LogcatStartMessage({filters: {tag: data.filters[0], priority: 1}})) + ]) + }) .on('logcat.stop', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.LogcatStopMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.LogcatStopMessage()) + ]) + }) .on('logcat.stopIos', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.LogcatStopMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.LogcatStopMessage()) + ]) + }) .on('connect.start', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.ConnectStartMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.ConnectStartMessage()) + ]) + }) .on('connect.startIos', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.ConnectStartMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.ConnectStartMessage()) + ]) + }) .on('connect.stop', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.ConnectStopMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.ConnectStopMessage()) + ]) + }) .on('browser.open', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.BrowserOpenMessage(data)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.BrowserOpenMessage(data)) + ]) + }) .on('browser.openIos', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.BrowserOpenMessage(data)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.BrowserOpenMessage(data)) + ]) + }) .on('browser.clear', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.BrowserClearMessage(data)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.BrowserClearMessage(data)) + ]) + }) .on('store.open', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.StoreOpenMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.StoreOpenMessage()) + ]) + }) .on('store.openIos', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.StoreOpenMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.StoreOpenMessage()) + ]) + }) .on('settings.open', function(channel, responseChannel) { - push.send([ - channel - , wireutil.envelope(new wire.DashboardOpenMessage()) - ]) - }) + push.send([ + channel + , wireutil.envelope(new wire.DashboardOpenMessage()) + ]) + }) .on('screen.capture', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.ScreenCaptureMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.ScreenCaptureMessage()) + ]) + }) .on('screen.captureIos', function(channel, responseChannel) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.ScreenCaptureMessage()) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.ScreenCaptureMessage()) + ]) + }) .on('fs.retrieve', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.FileSystemGetMessage(data)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.FileSystemGetMessage(data)) + ]) + }) .on('fs.list', function(channel, responseChannel, data) { - joinChannel(responseChannel) - push.send([ - channel - , wireutil.transaction(responseChannel, new wire.FileSystemListMessage(data)) - ]) - }) + joinChannel(responseChannel) + push.send([ + channel + , wireutil.transaction(responseChannel, new wire.FileSystemListMessage(data)) + ]) + }) .on('policy.accept', function(data) { - dbapi.acceptPolicy(user.email) - }) + dbapi.acceptPolicy(user.email) + }) }) .finally(function() { // Clean up all listeners and subscriptions - channelRouter.removeListener(wireutil.global, messageListener) - channels.forEach(function(channel) { - channelRouter.removeListener(channel, messageListener) - sub.unsubscribe(channel) + channelRouter.removeListener(wireutil.global, messageListener) + channels.forEach(function(channel) { + channelRouter.removeListener(channel, messageListener) + sub.unsubscribe(channel) + }) + socket.disconnect(true) }) - socket.disconnect(true) - }) .catch(function(err) { - log.error('Client had an error, disconnecting due to probable loss of integrity', err.stack) - }) + log.error('Client had an error, disconnecting due to probable loss of integrity', err.stack) + }) }) lifecycle.observe(function() { [push, sub].forEach(function(sock) { diff --git a/lib/units/websocket/middleware/auth.js b/lib/units/websocket/middleware/auth.js index af967f10e0..4766ab5caf 100644 --- a/lib/units/websocket/middleware/auth.js +++ b/lib/units/websocket/middleware/auth.js @@ -1,21 +1,33 @@ -import dbapi from '../../../db/api.mjs' -export default (function(socket, next) { - var req = socket.request - var token = req.session.jwt - if (token) { - return dbapi.loadUser(token.email) - .then(function(user) { - if (user) { - req.user = user - next() - } - else { - next(new Error('Invalid user')) - } - }) - .catch(next) - } - else { - next(new Error('Missing authorization token')) +import * as dbapi from '../../../db/api.js' +import * as jwtutil from '../../../util/jwtutil.js' +import * as cookie from 'cookie' + +export default (function(options) { + return function(socket, next) { + let req = socket.request + let token + const cookies = cookie.parse(req.headers.cookie) + if (cookies.token) { + token = jwtutil.decode(cookies.token, options.secret) + } + else { + token = req.session.jwt + } + if (token) { + return dbapi.loadUser(token.email) + .then(function(user) { + if (user) { + req.user = user + next() + } + else { + next(new Error('Invalid user')) + } + }) + .catch(next) + } + else { + next(new Error('Missing authorization token')) + } } }) diff --git a/lib/util/apiutil.js b/lib/util/apiutil.js index 15187f8b36..a0b8202491 100644 --- a/lib/util/apiutil.js +++ b/lib/util/apiutil.js @@ -5,66 +5,74 @@ import datautil from './datautil.js' import wireutil from '../wire/util.js' import wire from '../wire/index.js' import uuid from 'uuid' -const apiutil = Object.create(null) const log = logger.createLogger('api:controllers:apiutil') -apiutil.PENDING = 'pending' -apiutil.READY = 'ready' -apiutil.WAITING = 'waiting' -apiutil.NOT_FOUND = 'not found' -apiutil.BOOKABLE = 'bookable' -apiutil.STANDARD = 'standard' -apiutil.ONCE = 'once' -apiutil.DEBUG = 'debug' -apiutil.ORIGIN = 'origin' -apiutil.STANDARDIZABLE = 'standardizable' -apiutil.ROOT = 'root' -apiutil.ADMIN = 'admin' -apiutil.USER = 'user' -apiutil.STF_ADMIN_EMAIL = 'administrator@fakedomain.com' -apiutil.ONE_SECOND = 1000 -apiutil.ONE_MN = apiutil.ONE_SECOND * 60 -apiutil.FIVE_MN = 300 * 1000 -apiutil.TEN_MINUTES = apiutil.FIVE_MN * 2 -apiutil.QUARTER_MINUTES = apiutil.FIVE_MN * 3 -apiutil.HALF_HOUR = 1800 * 1000 -apiutil.ONE_HOUR = 3600 * 1000 -apiutil.ONE_DAY = 24 * apiutil.ONE_HOUR -apiutil.ONE_WEEK = 7 * apiutil.ONE_DAY -apiutil.ONE_MONTH = 30 * apiutil.ONE_DAY -apiutil.ONE_QUATER = 3 * apiutil.ONE_MONTH -apiutil.ONE_HALF_YEAR = 6 * apiutil.ONE_MONTH -apiutil.ONE_YEAR = 365 * apiutil.ONE_DAY -apiutil.TEN_YEARS = apiutil.ONE_YEAR * 10 -apiutil.MAX_USER_GROUPS_NUMBER = 10 -apiutil.MAX_USER_GROUPS_DURATION = 15 * apiutil.ONE_DAY -apiutil.MAX_USER_GROUPS_REPETITIONS = 10 -apiutil.CLASS_DURATION = { +export const PENDING = 'pending' +export const READY = 'ready' +export const WAITING = 'waiting' +export const NOT_FOUND = 'not found' +export const BOOKABLE = 'bookable' +export const STANDARD = 'standard' +export const ONCE = 'once' +export const DEBUG = 'debug' +export const ORIGIN = 'origin' +export const STANDARDIZABLE = 'standardizable' +export const ROOT = 'root' +export const ADMIN = 'admin' +export const USER = 'user' +export const STF_ADMIN_EMAIL = 'administrator@fakedomain.com' +export const ONE_SECOND = 1000 +export const ONE_MN = ONE_SECOND * 60 +export const FIVE_MN = 300 * 1000 +export const TEN_MINUTES = FIVE_MN * 2 +export const QUARTER_MINUTES = FIVE_MN * 3 +export const HALF_HOUR = 1800 * 1000 +export const ONE_HOUR = 3600 * 1000 +export const ONE_DAY = 24 * ONE_HOUR +export const ONE_WEEK = 7 * ONE_DAY +export const ONE_MONTH = 30 * ONE_DAY +export const ONE_QUATER = 3 * ONE_MONTH +export const ONE_HALF_YEAR = 6 * ONE_MONTH +export const ONE_YEAR = 365 * ONE_DAY +export const TEN_YEARS = ONE_YEAR * 10 +export const MAX_USER_GROUPS_NUMBER = 10 +export const MAX_USER_GROUPS_DURATION = 15 * ONE_DAY +export const MAX_USER_GROUPS_REPETITIONS = 10 +export const CLASS_DURATION = { once: Infinity , bookable: Infinity , standard: Infinity - , hourly: apiutil.ONE_HOUR - , daily: apiutil.ONE_DAY - , weekly: apiutil.ONE_WEEK - , monthly: apiutil.ONE_MONTH - , quaterly: apiutil.ONE_QUATER - , halfyearly: apiutil.ONE_HALF_YEAR - , yearly: apiutil.ONE_YEAR - , debug: apiutil.FIVE_MN -} -apiutil.GRPC_TIMEOUT = apiutil.ONE_SECOND * 10 -apiutil.GRPC_WAIT_TIMEOUT = apiutil.GRPC_TIMEOUT * 2 -apiutil.INSTALL_APK_WAIT = apiutil.ONE_MN * 9 -apiutil.isOriginGroup = function(_class) { - return _class === apiutil.BOOKABLE || _class === apiutil.STANDARD -} -apiutil.isAdminGroup = function(_class) { - return apiutil.isOriginGroup(_class) || _class === apiutil.DEBUG -} -apiutil.internalError = function(res, ...args) { - log.error.apply(log, args) - apiutil.respond(res, 500, 'Internal Server Error') + , hourly: ONE_HOUR + , daily: ONE_DAY + , weekly: ONE_WEEK + , monthly: ONE_MONTH + , quaterly: ONE_QUATER + , halfyearly: ONE_HALF_YEAR + , yearly: ONE_YEAR + , debug: FIVE_MN +} +export const GRPC_TIMEOUT = ONE_SECOND * 10 +export const GRPC_WAIT_TIMEOUT = GRPC_TIMEOUT * 2 +export const INSTALL_APK_WAIT = ONE_MN * 9 +export const isOriginGroup = function(_class) { + return _class === BOOKABLE || _class === STANDARD +} +export const isAdminGroup = function(_class) { + return isOriginGroup(_class) || _class === DEBUG +} +export const truncateString = (str, num) =>{ + if (str.length > num) { + return str.slice(0, num) + '...' + } + else { + return str + } } -apiutil.respond = function(res, code, message, data) { +export const respond = function(res, code, message, data) { + if (res.headersSent) { + log.error('Headers already sent when trying to respond with', code, message, data) + return res + } + log.info('Responding with', code, message, truncateString(JSON.stringify(data) + '', 600)) const status = code >= 200 && code < 300 const response = { success: status @@ -82,26 +90,31 @@ apiutil.respond = function(res, code, message, data) { res.status(code).json(response) return res } -apiutil.publishGroup = function(group) { +export const internalError = function(res, ...args) { + log.error.apply(log, args) + console.trace('error occured here') + respond(res, 500, 'Internal Server Error') +} +export const publishGroup = function(group) { delete group.createdAt delete group.ticket return group } -apiutil.publishDevice = function(device, req, isGenerator) { +export const publishDevice = function(device, req, isGenerator) { let user = req.user datautil.normalize(device, user, isGenerator) return device } -apiutil.publishUser = function(user) { +export const publishUser = function(user) { // delete user.groups.lock return user } -apiutil.publishAccessToken = function(token) { +export const publishAccessToken = function(token) { delete token.email delete token.jwt return token } -apiutil.filterDevice = function(req, device) { +export const filterDevice = function(req, device) { const fields = req.query.fields let isGenerator = false if (req.headers.is_generator === '1') { @@ -113,39 +126,39 @@ apiutil.filterDevice = function(req, device) { ]) } if (fields) { - return _.pick(apiutil.publishDevice(device, req, isGenerator), fields.split(',')) + return _.pick(publishDevice(device, req, isGenerator), fields.split(',')) } - return apiutil.publishDevice(device, req, isGenerator) + return publishDevice(device, req, isGenerator) } -apiutil.computeDuration = function(group, deviceNumber) { +export const computeDuration = function(group, deviceNumber) { return (group.devices.length + deviceNumber) * (group.dates[0].stop - group.dates[0].start) * (group.repetitions + 1) } -apiutil.lightComputeStats = function(res, stats) { +export const lightComputeStats = function(res, stats) { if (stats.locked) { - apiutil.respond(res, 429, 'Too many requests. Destination object is locked') + respond(res, 429, 'Too many requests. Destination object is locked') return Promise.reject('busy') } return 'not found' } -apiutil.computeStats = function(res, stats, objectName, ...lock) { +export const computeStats = function(res, stats, objectName, ...lock) { if (stats.modifiedCount === 0) { if (stats.skipped) { - return apiutil.respond(res, 404, `Not Found (${objectName})`) + return respond(res, 404, `Not Found (${objectName})`) } if (stats.locked) { - return apiutil.respond(res, 429, 'Too many requests. Destination object is locked') + return respond(res, 429, 'Too many requests. Destination object is locked') } console.trace(`403 from here Forbidden (${objectName} not changed)`) - return apiutil.respond(res, 403, `Forbidden (${objectName} not changed)`) + return respond(res, 403, `Forbidden (${objectName} not changed)`) } if (lock.length) { lock[0][objectName] = stats.changes[0].new_val } return true } -apiutil.lockResult = function(stats) { +export const lockResult = function(stats) { let result = {status: false, data: stats} if (stats.modifiedCount > 0 || stats.matchedCount > 0) { result.status = true @@ -156,8 +169,8 @@ apiutil.lockResult = function(stats) { } return result } -apiutil.lockDeviceResult = function(stats, fn, groups, serial) { - const result = apiutil.lockResult(stats) +export const lockDeviceResult = function(stats, fn, groups, serial) { + const result = lockResult(stats) if (!result.status) { return fn(groups, serial).then(function(devices) { if (!devices.length) { @@ -169,7 +182,26 @@ apiutil.lockDeviceResult = function(stats, fn, groups, serial) { } return result } -apiutil.setIntervalWrapper = function(fn, numTimes, delay) { + +const sleep = (delay) => new Promise((resolve) => { + setTimeout(resolve, delay) +}) + +export const setIntervalWrapper = async function(fn, numTimes, delay) { + let lastResult = null + for (let attempt = 0; attempt < numTimes; attempt++) { + log.warn(`Trying ${fn.name} ${attempt + 1}/${numTimes}`) + lastResult = await fn() + if (lastResult.status) { + return lastResult.data + } + log.warn(`Retrying ${fn.name} ${attempt + 1}/${numTimes}`) + await sleep(delay) + } + return Promise.reject(lastResult?.data) +} + +export const setIntervalWrapperOld = async function(fn, numTimes, delay) { return fn().then(function(result) { if (result.status) { return result.data @@ -187,14 +219,14 @@ apiutil.setIntervalWrapper = function(fn, numTimes, delay) { } }) .catch(function(err) { - clearInterval(interval) - reject(err) - }) + clearInterval(interval) + reject(err) + }) }, delay) }) }) } -apiutil.redirectApiWrapper = function(field, fn, req, res) { +export const redirectApiWrapper = function(field, fn, req, res) { // what the actual fuck if (typeof req.body === 'undefined') { req.body = {} @@ -203,16 +235,16 @@ apiutil.redirectApiWrapper = function(field, fn, req, res) { req.query.redirected = true fn(req, res) } -apiutil.computeGroupDates = function(lifeTime, _class, repetitions) { +export const computeGroupDates = function(lifeTime, _class, repetitions) { const dates = new Array(lifeTime) for (let repetition = 1, currentLifeTime = { start: new Date(lifeTime.start.getTime()) , stop: new Date(lifeTime.stop.getTime()) }; repetition <= repetitions; repetition++) { currentLifeTime.start = new Date(currentLifeTime.start.getTime() + - apiutil.CLASS_DURATION[_class]) + CLASS_DURATION[_class]) currentLifeTime.stop = new Date(currentLifeTime.stop.getTime() + - apiutil.CLASS_DURATION[_class]) + CLASS_DURATION[_class]) dates.push({ start: new Date(currentLifeTime.start.getTime()) , stop: new Date(currentLifeTime.stop.getTime()) @@ -220,18 +252,75 @@ apiutil.computeGroupDates = function(lifeTime, _class, repetitions) { } return dates } -apiutil.checkBodyParameter = function(body, parameter) { +export const checkBodyParameter = function(body, parameter) { return typeof body !== 'undefined' && typeof body[parameter] !== 'undefined' } -apiutil.getBodyParameter = function(body, parameter) { +export const getBodyParameter = function(body, parameter) { let undef - return apiutil.checkBodyParameter(body, parameter) ? body[parameter] : undef + return checkBodyParameter(body, parameter) ? body[parameter] : undef } -apiutil.checkQueryParameter = function(parameter) { +export const checkQueryParameter = function(parameter) { return typeof parameter !== 'undefined' && typeof parameter.value !== 'undefined' } -apiutil.getQueryParameter = function(parameter) { +export const getQueryParameter = function(parameter) { let undef - return apiutil.checkQueryParameter(parameter) ? (parameter.value ?? parameter) : undef + return checkQueryParameter(parameter) ? (parameter.value ?? parameter) : undef +} +export default { + PENDING + , READY + , WAITING + , NOT_FOUND + , BOOKABLE + , STANDARD + , ONCE + , DEBUG + , ORIGIN + , STANDARDIZABLE + , ROOT + , ADMIN + , USER + , STF_ADMIN_EMAIL + , ONE_SECOND + , ONE_MN + , FIVE_MN + , TEN_MINUTES + , QUARTER_MINUTES + , HALF_HOUR + , ONE_HOUR + , ONE_DAY + , ONE_WEEK + , ONE_MONTH + , ONE_QUATER + , ONE_HALF_YEAR + , ONE_YEAR + , TEN_YEARS + , MAX_USER_GROUPS_NUMBER + , MAX_USER_GROUPS_DURATION + , MAX_USER_GROUPS_REPETITIONS + , CLASS_DURATION + , GRPC_TIMEOUT + , GRPC_WAIT_TIMEOUT + , INSTALL_APK_WAIT + , isOriginGroup + , isAdminGroup + , respond + , internalError + , publishGroup + , publishDevice + , publishUser + , publishAccessToken + , filterDevice + , computeDuration + , lightComputeStats + , computeStats + , lockResult + , lockDeviceResult + , setIntervalWrapper + , redirectApiWrapper + , computeGroupDates + , checkBodyParameter + , getBodyParameter + , checkQueryParameter + , getQueryParameter } -export default apiutil diff --git a/lib/util/asciiparser.js b/lib/util/asciiparser.js index eaacf37cfc..7386e40c95 100755 --- a/lib/util/asciiparser.js +++ b/lib/util/asciiparser.js @@ -1,10 +1,10 @@ export const asciiparser = function(key) { switch (key) { - case 'enter': - return '\r' - case 'del': - return '\x08' - default: - return key + case 'enter': + return '\r' + case 'del': + return '\x08' + default: + return key } } diff --git a/lib/util/bundletool.js b/lib/util/bundletool.js index 07bd226612..1abcd4703a 100644 --- a/lib/util/bundletool.js +++ b/lib/util/bundletool.js @@ -104,35 +104,35 @@ export default (function(options) { log.info('AAB detected') checkIfJava() .then(function() { - if (!fs.existsSync(keystore.ksPath)) { - cp.spawnSync('keytool', [ - '-genkey' - , '-noprompt' - , '-keystore', keystore.ksPath - , '-alias', keystore.ksKeyAlias - , '-keyalg', keystore.ksKeyalg - , '-keysize', keystore.ksKeysize - , '-storepass', keystore.ksPass - , '-keypass', keystore.ksKeyPass - , '-dname', keystore.ksDname - , '-validity', keystore.ksValidity - ]) - } - }) + if (!fs.existsSync(keystore.ksPath)) { + cp.spawnSync('keytool', [ + '-genkey' + , '-noprompt' + , '-keystore', keystore.ksPath + , '-alias', keystore.ksKeyAlias + , '-keyalg', keystore.ksKeyalg + , '-keysize', keystore.ksKeysize + , '-storepass', keystore.ksPass + , '-keypass', keystore.ksKeyPass + , '-dname', keystore.ksDname + , '-validity', keystore.ksValidity + ]) + } + }) .then(function() { - if (!fs.existsSync(keystore.ksPath)) { - reject('Keystore not found') - } - else if (!fs.existsSync(bundletoolFilePath)) { - reject('bundletool not found') - } - else { - convert() - } - }) + if (!fs.existsSync(keystore.ksPath)) { + reject('Keystore not found') + } + else if (!fs.existsSync(bundletoolFilePath)) { + reject('bundletool not found') + } + else { + convert() + } + }) .catch(function(err) { - reject(err) - }) + reject(err) + }) } else { resolve(bundle) diff --git a/lib/util/devutil.js b/lib/util/devutil.js index 24e0b60e94..a9dc466963 100644 --- a/lib/util/devutil.js +++ b/lib/util/devutil.js @@ -12,25 +12,25 @@ devutil.executeShellCommand = function(adb, serial, command) { devutil.ensureUnusedLocalSocket = function(adb, serial, sock) { return adb.getDevice(serial).openLocal(sock) .then(function(conn) { - conn.end() - throw new Error(util.format('Local socket "%s" should be unused', sock)) - }) + conn.end() + throw new Error(util.format('Local socket "%s" should be unused', sock)) + }) .catch(closedError, function() { - return Promise.resolve(sock) - }) + return Promise.resolve(sock) + }) } devutil.waitForLocalSocket = function(adb, serial, sock) { return adb.getDevice(serial).openLocal(sock) .then(function(conn) { - conn.sock = sock - return conn - }) + conn.sock = sock + return conn + }) .catch(closedError, function() { - return Promise.delay(100) - .then(function() { - return devutil.waitForLocalSocket(adb, serial, sock) + return Promise.delay(100) + .then(function() { + return devutil.waitForLocalSocket(adb, serial, sock) + }) }) - }) } devutil.listPidsByComm = function(adb, serial, comm, bin) { var users = { @@ -43,25 +43,25 @@ devutil.listPidsByComm = function(adb, serial, comm, bin) { var showTotalPid = false out.pipe(split()) .on('data', function(chunk) { - if (header) { - header = false - } - else { - var cols = chunk.toString().split(/\s+/) - if (!showTotalPid && cols[0] === 'root') { - showTotalPid = true + if (header) { + header = false } - // last column of output would be command name containing absolute path like '/data/local/tmp/minicap' - // or just binary name like 'minicap', it depends on device/ROM - var lastCol = cols.pop() - if ((lastCol === comm || lastCol === bin) && users[cols[0]]) { - pids.push(Number(cols[1])) + else { + var cols = chunk.toString().split(/\s+/) + if (!showTotalPid && cols[0] === 'root') { + showTotalPid = true + } + // last column of output would be command name containing absolute path like '/data/local/tmp/minicap' + // or just binary name like 'minicap', it depends on device/ROM + var lastCol = cols.pop() + if ((lastCol === comm || lastCol === bin) && users[cols[0]]) { + pids.push(Number(cols[1])) + } } - } - }) + }) .on('end', function() { - resolve({showTotalPid: showTotalPid, pids: pids}) - }) + resolve({showTotalPid: showTotalPid, pids: pids}) + }) }) } return adb.getDevice(serial).shell('ps 2>/dev/null') @@ -69,50 +69,50 @@ devutil.listPidsByComm = function(adb, serial, comm, bin) { .then(function(res) { // return pids if process can be found in the output of 'ps' command // or 'ps' command has already displayed all the processes including processes launched by root user - if (res.showTotalPid || res.pids.length > 0) { - return Promise.resolve(res.pids) - } - // otherwise try to run 'ps -elf' - else { - return adb.getDevice(serial).shell('ps -lef 2>/dev/null') - .then(findProcess) - .then(function(res) { + if (res.showTotalPid || res.pids.length > 0) { return Promise.resolve(res.pids) - }) - } - }) + } + // otherwise try to run 'ps -elf' + else { + return adb.getDevice(serial).shell('ps -lef 2>/dev/null') + .then(findProcess) + .then(function(res) { + return Promise.resolve(res.pids) + }) + } + }) } devutil.waitForProcsToDie = function(adb, serial, comm, bin) { return devutil.listPidsByComm(adb, serial, comm, bin) .then(function(pids) { - if (pids.length) { - return Promise.delay(100) - .then(function() { - return devutil.waitForProcsToDie(adb, serial, comm, bin) - }) - } - }) + if (pids.length) { + return Promise.delay(100) + .then(function() { + return devutil.waitForProcsToDie(adb, serial, comm, bin) + }) + } + }) } devutil.killProcsByComm = function(adb, serial, comm, bin, mode) { return devutil.listPidsByComm(adb, serial, comm, bin, mode) .then(function(pids) { - if (!pids.length) { - return Promise.resolve() - } - return adb.getDevice(serial).shell(['kill', mode || -15].concat(pids)) - .then(function(out) { - return new Promise(function(resolve) { - out.on('end', resolve) - }) - }) - .then(function() { - return devutil.waitForProcsToDie(adb, serial, comm, bin) - }) - .timeout(2000) - .catch(function() { - return devutil.killProcsByComm(adb, serial, comm, bin, -9) + if (!pids.length) { + return Promise.resolve() + } + return adb.getDevice(serial).shell(['kill', mode || -15].concat(pids)) + .then(function(out) { + return new Promise(function(resolve) { + out.on('end', resolve) + }) + }) + .then(function() { + return devutil.waitForProcsToDie(adb, serial, comm, bin) + }) + .timeout(2000) + .catch(function() { + return devutil.killProcsByComm(adb, serial, comm, bin, -9) + }) }) - }) } devutil.makeIdentity = function(serial, properties) { let model = properties['ro.product.model'] diff --git a/lib/util/fakedevice.js b/lib/util/fakedevice.js index a659dc3e63..189a12b6bc 100644 --- a/lib/util/fakedevice.js +++ b/lib/util/fakedevice.js @@ -1,7 +1,7 @@ import util from 'util' import uuid from 'uuid' import _ from 'lodash' -import dbapi from '../db/api.mjs' +import * as dbapi from '../db/api.js' export const generate = function(wantedModel) { // no base64 because some characters as '=' or '/' are not compatible through API (delete devices) const serial = 'fake-' + util.format('%s', uuid.v4()).replace(/-/g, '') @@ -10,47 +10,46 @@ export const generate = function(wantedModel) { name: 'FAKE/1' , channel: '*fake' } - , status: 'OFFLINE' + , status: 3 + , ready: true + , present: true }) .then(function() { - var model = wantedModel || 'FakeDeviceModel' - return dbapi.saveDeviceIdentity(serial, { - platform: 'Android' - , manufacturer: 'Foo Electronics' - , operator: 'Loss Networks' - , model: model - , version: '4.1.2' - , abi: 'armeabi-v7a' - , sdk: (8 + Math.floor(Math.random() * 12)).toString() // string required! - , display: { - density: 3 - , fps: 60 - , height: 1920 - , id: 0 - , rotation: 0 - , secure: true - , url: '/404.jpg' - , width: 1080 - , xdpi: 442 - , ydpi: 439 - } - , phone: { - iccid: '1234567890123456789' - , imei: '123456789012345' - , imsi: '123456789012345' - , network: 'LTE' - , phoneNumber: '0000000000' - } - , product: model - , cpuPlatform: 'msm8996' - , openGLESVersion: '3.1' - , marketName: 'Bar F9+' - , macAddress: '123abc' - , ram: 0 + var model = wantedModel || 'FakeDeviceModel' + return dbapi.saveDeviceIdentity(serial, { + platform: 'Android' + , manufacturer: 'Foo Electronics' + , operator: 'Loss Networks' + , model: model + , version: '4.1.2' + , abi: 'armeabi-v7a' + , sdk: (8 + Math.floor(Math.random() * 12)).toString() // string required! + , display: { + density: 3 + , fps: 60 + , height: 1920 + , id: 0 + , rotation: 0 + , secure: true + , url: '/404.jpg' + , width: 1080 + , xdpi: 442 + , ydpi: 439 + } + , phone: { + iccid: '1234567890123456789' + , imei: '123456789012345' + , imsi: '123456789012345' + , network: 'LTE' + , phoneNumber: '0000000000' + } + , product: model + , cpuPlatform: 'msm8996' + , openGLESVersion: '3.1' + , marketName: 'Bar F9+' + , macAddress: '123abc' + , ram: 0 + }) }) - }) - .then(function() { - return dbapi.setDeviceAbsent(serial) - }) .return(serial) } diff --git a/lib/util/fakegroup.js b/lib/util/fakegroup.js index fe9161a2fb..77b428c812 100644 --- a/lib/util/fakegroup.js +++ b/lib/util/fakegroup.js @@ -1,7 +1,7 @@ import util from 'util' import uuid from 'uuid' -import dbapi from '../db/api.mjs' -import apiutil from './apiutil.js' +import * as dbapi from '../db/api.js' +import * as apiutil from './apiutil.js' export const generate = function() { return dbapi.getRootGroup().then(function(rootGroup) { const now = Date.now() @@ -23,10 +23,10 @@ export const generate = function() { , state: apiutil.READY }) .then(function(group) { - if (group) { - return group.id - } - throw new Error('Forbidden (groups number quota is reached)') - }) + if (group) { + return group.id + } + throw new Error('Forbidden (groups number quota is reached)') + }) }) } diff --git a/lib/util/fakeuser.js b/lib/util/fakeuser.js index ef64621a1d..70f2fcc09b 100644 --- a/lib/util/fakeuser.js +++ b/lib/util/fakeuser.js @@ -1,6 +1,6 @@ import util from 'util' import uuid from 'uuid' -import dbapi from '../db/api.mjs' +import * as dbapi from '../db/api.js' export const generate = function() { const name = 'fakeuser-' + util.format('%s', uuid.v4()).replace(/-/g, '') const email = name + '@openstf.com' diff --git a/lib/util/grouputil.js b/lib/util/grouputil.js index f5340bfd8b..e718e2b8c8 100644 --- a/lib/util/grouputil.js +++ b/lib/util/grouputil.js @@ -31,23 +31,23 @@ export const match = Promise.method(function(capabilities, requirements) { throw new RequirementMismatchError(req.name) } switch (req.type) { - case wire.RequirementType.SEMVER: - if (!semver.satisfies(capability, req.value)) { - throw new RequirementMismatchError(req.name) - } - break - case wire.RequirementType.GLOB: - if (!minimatch(capability, req.value)) { - throw new RequirementMismatchError(req.name) - } - break - case wire.RequirementType.EXACT: - if (capability !== req.value) { - throw new RequirementMismatchError(req.name) - } - break - default: + case wire.RequirementType.SEMVER: + if (!semver.satisfies(capability, req.value)) { throw new RequirementMismatchError(req.name) + } + break + case wire.RequirementType.GLOB: + if (!minimatch(capability, req.value)) { + throw new RequirementMismatchError(req.name) + } + break + case wire.RequirementType.EXACT: + if (capability !== req.value) { + throw new RequirementMismatchError(req.name) + } + break + default: + throw new RequirementMismatchError(req.name) } return true }) diff --git a/lib/util/instrument.mjs b/lib/util/instrument.mjs new file mode 100644 index 0000000000..5fd0c6ac6e --- /dev/null +++ b/lib/util/instrument.mjs @@ -0,0 +1,23 @@ +import * as Sentry from '@sentry/node' + +const DSN = process.env.SENTRY_DSN +const SAMPLE_RATE = Number(process.env.SENTRY_TRACES_SAMPLE_RATE) || 0 +const ENVIRONMENT = process.env.SENTRY_ENVIRONMENT || 'unset' + +Sentry.init({ + dsn: DSN + , environment: ENVIRONMENT + // Tracing + // Add Tracing by setting tracesSampleRate + // We recommend adjusting this value in production + , tracesSampleRate: SAMPLE_RATE + , integrations: [ + Sentry.mongoIntegration({ + enhancedDatabaseReporting: true + }) + ] +}) +console.log(`Initialized sentry for environment: ${ENVIRONMENT}`) +if (SAMPLE_RATE === 0) { + console.warn('Warning: Sentry sample_rate is 0') +} diff --git a/lib/util/jwtutil.js b/lib/util/jwtutil.js index 69bab84f95..2eb9c055ba 100644 --- a/lib/util/jwtutil.js +++ b/lib/util/jwtutil.js @@ -4,7 +4,7 @@ import _ from 'lodash' export const encode = function(options) { assert.ok(options.payload, 'payload required') assert.ok(options.secret, 'secret required') - var header = { + let header = { alg: 'HS256' } if (options.header) { @@ -20,10 +20,10 @@ export const decode = function(payload, secret) { if (!jws.verify(payload, 'HS256', secret)) { return null } - var decoded = jws.decode(payload, { + let decoded = jws.decode(payload, { json: true }) - var exp = decoded.header.exp + let exp = decoded.header.exp if (exp && exp <= Date.now()) { return null } diff --git a/lib/util/keyutil.js b/lib/util/keyutil.js index 91d29756fd..8d73dfc910 100644 --- a/lib/util/keyutil.js +++ b/lib/util/keyutil.js @@ -15,401 +15,401 @@ keyutil.parseKeyCharacterMap = function(stream) { } function parse(char) { switch (state) { - case 'comment_before_type_t': - if (char === '\n') { - state = 'type_t' - break - } + case 'comment_before_type_t': + if (char === '\n') { + state = 'type_t' + break + } + return true + case 'type_t': + if (char === '\n') { return true - case 'type_t': - if (char === '\n') { - return true - } - if (char === '#') { - state = 'comment_before_type_t' - return true - } - if (char === 'k') { - state = 'key_k' - return parse(char) - } - if (char === 't') { - state = 'type_y' - return true - } - return fail(char, state) - case 'type_y': - if (char === 'y') { - state = 'type_p' - return true - } - return fail(char, state) - case 'type_p': - if (char === 'p') { - state = 'type_e' - return true - } - return fail(char, state) - case 'type_e': - if (char === 'e') { - state = 'type_name_start' - keymap.type = '' - return true - } - return fail(char, state) - case 'type_name_start': - if (char === ' ') { - return true - } - if (char >= 'A' && char <= 'Z') { - keymap.type += char - state = 'type_name_continued' - return true - } - return fail(char, state) - case 'type_name_continued': - if (char === '\n') { - // Could have more of these, although it doesn't make much sense - state = 'type_t' - return true - } - if (char >= 'A' && char <= 'Z') { - keymap.type += char - return true - } - return fail(char, state) - case 'comment_before_key_k': - if (char === '\n') { - state = 'key_k' - break - } + } + if (char === '#') { + state = 'comment_before_type_t' return true - case 'key_k': - if (char === '\n') { - return true - } - if (char === '#') { - state = 'comment_before_key_k' - return true - } - if (char === 'k') { - state = 'key_e' - return true - } - return fail(char, state) - case 'key_e': - if (char === 'e') { - state = 'key_y' - return true - } - return fail(char, state) - case 'key_y': - if (char === 'y') { - state = 'key_name_start' - return true - } - return fail(char, state) - case 'key_name_start': - if (char === ' ') { - return true - } - if ((char >= '0' && char <= '9') || + } + if (char === 'k') { + state = 'key_k' + return parse(char) + } + if (char === 't') { + state = 'type_y' + return true + } + return fail(char, state) + case 'type_y': + if (char === 'y') { + state = 'type_p' + return true + } + return fail(char, state) + case 'type_p': + if (char === 'p') { + state = 'type_e' + return true + } + return fail(char, state) + case 'type_e': + if (char === 'e') { + state = 'type_name_start' + keymap.type = '' + return true + } + return fail(char, state) + case 'type_name_start': + if (char === ' ') { + return true + } + if (char >= 'A' && char <= 'Z') { + keymap.type += char + state = 'type_name_continued' + return true + } + return fail(char, state) + case 'type_name_continued': + if (char === '\n') { + // Could have more of these, although it doesn't make much sense + state = 'type_t' + return true + } + if (char >= 'A' && char <= 'Z') { + keymap.type += char + return true + } + return fail(char, state) + case 'comment_before_key_k': + if (char === '\n') { + state = 'key_k' + break + } + return true + case 'key_k': + if (char === '\n') { + return true + } + if (char === '#') { + state = 'comment_before_key_k' + return true + } + if (char === 'k') { + state = 'key_e' + return true + } + return fail(char, state) + case 'key_e': + if (char === 'e') { + state = 'key_y' + return true + } + return fail(char, state) + case 'key_y': + if (char === 'y') { + state = 'key_name_start' + return true + } + return fail(char, state) + case 'key_name_start': + if (char === ' ') { + return true + } + if ((char >= '0' && char <= '9') || (char >= 'A' && char <= 'Z')) { - keymap.keys.push(lastKey = { - key: char - , rules: [] - }) - state = 'key_name_continued' - return true - } - return fail(char, state) - case 'key_name_continued': - if (char === ' ') { - state = 'key_start_block' - return true - } - if ((char >= '0' && char <= '9') || + keymap.keys.push(lastKey = { + key: char + , rules: [] + }) + state = 'key_name_continued' + return true + } + return fail(char, state) + case 'key_name_continued': + if (char === ' ') { + state = 'key_start_block' + return true + } + if ((char >= '0' && char <= '9') || (char >= 'A' && char <= 'Z') || (char === '_')) { - lastKey.key += char - return true - } - return fail(char, state) - case 'key_start_block': - if (char === ' ') { - return true - } - if (char === '{') { - state = 'filter_name_start' - return true - } - return fail(char, state) - case 'filter_name_start': - if (char === '\n' || char === '\t' || char === ' ') { - return true - } - if (char === '}') { - state = 'key_k' - return true - } - if (char >= 'a' && char <= 'z') { - lastKey.rules.push(lastRule = { - modifiers: [lastModifier = { - type: char - }] - , behaviors: [] - }) - state = 'filter_name_continued' - return true - } - return fail(char, state) - case 'filter_name_continued': - if (char === ':') { - state = 'filter_behavior_start' - return true - } - if (char === ',') { - state = 'filter_name_or_start' - return true - } - if (char === '+') { - state = 'filter_name_and_start' - return true - } - if (char >= 'a' && char <= 'z') { - lastModifier.type += char - return true - } - return fail(char, state) - case 'filter_name_or_start': - if (char === ' ') { - return true - } - if (char >= 'a' && char <= 'z') { - lastKey.rules.push(lastRule = { - modifiers: [lastModifier = { - type: char - }] - , behaviors: lastRule.behaviors - }) - state = 'filter_name_continued' - return true - } - return fail(char, state) - case 'filter_name_and_start': - if (char === ' ') { - return true - } - if (char >= 'a' && char <= 'z') { - lastRule.modifiers.push(lastModifier = { + lastKey.key += char + return true + } + return fail(char, state) + case 'key_start_block': + if (char === ' ') { + return true + } + if (char === '{') { + state = 'filter_name_start' + return true + } + return fail(char, state) + case 'filter_name_start': + if (char === '\n' || char === '\t' || char === ' ') { + return true + } + if (char === '}') { + state = 'key_k' + return true + } + if (char >= 'a' && char <= 'z') { + lastKey.rules.push(lastRule = { + modifiers: [lastModifier = { type: char - }) - state = 'filter_name_continued' - return true - } - return fail(char, state) - case 'filter_behavior_literal': - if (char === '\\') { - state = 'filter_behavior_literal_escape' - return true - } - if (char !== "'") { - lastRule.behaviors.push({ - type: 'literal' - , value: char - }) - state = 'filter_behavior_literal_end' - return true - } - return fail(char, state) - case 'filter_behavior_literal_escape': - if (char === '\\' || char === '\'' || char === '"') { - lastRule.behaviors.push({ - type: 'literal' - , value: char - }) - state = 'filter_behavior_literal_end' - return true - } - if (char === 'n') { - lastRule.behaviors.push({ - type: 'literal' - , value: '\n' - }) - state = 'filter_behavior_literal_end' - return true - } - if (char === 't') { - lastRule.behaviors.push({ - type: 'literal' - , value: '\t' - }) - state = 'filter_behavior_literal_end' - return true - } - if (char === 'u') { - state = 'filter_behavior_literal_unicode_1' - return true - } - return fail(char, state) - case 'filter_behavior_literal_end': - if (char === '\'') { - state = 'filter_behavior_start' - return true - } - return fail(char, state) - case 'filter_behavior_start': - if (char === '\n') { - state = 'filter_name_start' - return true - } - if (char === ' ') { - return true - } - if (char === "'") { - state = 'filter_behavior_literal' - return true - } - if (char === 'n') { - state = 'filter_behavior_none_2' - return true - } - if (char === 'f') { - state = 'filter_behavior_fallback_2' - return true - } - return fail(char, state) - case 'filter_behavior_fallback_2': - if (char === 'a') { - state = 'filter_behavior_fallback_3' - return true - } - return fail(char, state) - case 'filter_behavior_fallback_3': - if (char === 'l') { - state = 'filter_behavior_fallback_4' - return true - } - return fail(char, state) - case 'filter_behavior_fallback_4': - if (char === 'l') { - state = 'filter_behavior_fallback_5' - return true - } - return fail(char, state) - case 'filter_behavior_fallback_5': - if (char === 'b') { - state = 'filter_behavior_fallback_6' - return true - } - return fail(char, state) - case 'filter_behavior_fallback_6': - if (char === 'a') { - state = 'filter_behavior_fallback_7' - return true - } - return fail(char, state) - case 'filter_behavior_fallback_7': - if (char === 'c') { - state = 'filter_behavior_fallback_8' - return true - } - return fail(char, state) - case 'filter_behavior_fallback_8': - if (char === 'k') { - state = 'filter_behavior_fallback_key_start' - return true - } - return fail(char, state) - case 'filter_behavior_fallback_key_start': - if (char === ' ') { - return true - } - if ((char >= '0' && char <= '9') || + }] + , behaviors: [] + }) + state = 'filter_name_continued' + return true + } + return fail(char, state) + case 'filter_name_continued': + if (char === ':') { + state = 'filter_behavior_start' + return true + } + if (char === ',') { + state = 'filter_name_or_start' + return true + } + if (char === '+') { + state = 'filter_name_and_start' + return true + } + if (char >= 'a' && char <= 'z') { + lastModifier.type += char + return true + } + return fail(char, state) + case 'filter_name_or_start': + if (char === ' ') { + return true + } + if (char >= 'a' && char <= 'z') { + lastKey.rules.push(lastRule = { + modifiers: [lastModifier = { + type: char + }] + , behaviors: lastRule.behaviors + }) + state = 'filter_name_continued' + return true + } + return fail(char, state) + case 'filter_name_and_start': + if (char === ' ') { + return true + } + if (char >= 'a' && char <= 'z') { + lastRule.modifiers.push(lastModifier = { + type: char + }) + state = 'filter_name_continued' + return true + } + return fail(char, state) + case 'filter_behavior_literal': + if (char === '\\') { + state = 'filter_behavior_literal_escape' + return true + } + if (char !== "'") { + lastRule.behaviors.push({ + type: 'literal' + , value: char + }) + state = 'filter_behavior_literal_end' + return true + } + return fail(char, state) + case 'filter_behavior_literal_escape': + if (char === '\\' || char === '\'' || char === '"') { + lastRule.behaviors.push({ + type: 'literal' + , value: char + }) + state = 'filter_behavior_literal_end' + return true + } + if (char === 'n') { + lastRule.behaviors.push({ + type: 'literal' + , value: '\n' + }) + state = 'filter_behavior_literal_end' + return true + } + if (char === 't') { + lastRule.behaviors.push({ + type: 'literal' + , value: '\t' + }) + state = 'filter_behavior_literal_end' + return true + } + if (char === 'u') { + state = 'filter_behavior_literal_unicode_1' + return true + } + return fail(char, state) + case 'filter_behavior_literal_end': + if (char === '\'') { + state = 'filter_behavior_start' + return true + } + return fail(char, state) + case 'filter_behavior_start': + if (char === '\n') { + state = 'filter_name_start' + return true + } + if (char === ' ') { + return true + } + if (char === "'") { + state = 'filter_behavior_literal' + return true + } + if (char === 'n') { + state = 'filter_behavior_none_2' + return true + } + if (char === 'f') { + state = 'filter_behavior_fallback_2' + return true + } + return fail(char, state) + case 'filter_behavior_fallback_2': + if (char === 'a') { + state = 'filter_behavior_fallback_3' + return true + } + return fail(char, state) + case 'filter_behavior_fallback_3': + if (char === 'l') { + state = 'filter_behavior_fallback_4' + return true + } + return fail(char, state) + case 'filter_behavior_fallback_4': + if (char === 'l') { + state = 'filter_behavior_fallback_5' + return true + } + return fail(char, state) + case 'filter_behavior_fallback_5': + if (char === 'b') { + state = 'filter_behavior_fallback_6' + return true + } + return fail(char, state) + case 'filter_behavior_fallback_6': + if (char === 'a') { + state = 'filter_behavior_fallback_7' + return true + } + return fail(char, state) + case 'filter_behavior_fallback_7': + if (char === 'c') { + state = 'filter_behavior_fallback_8' + return true + } + return fail(char, state) + case 'filter_behavior_fallback_8': + if (char === 'k') { + state = 'filter_behavior_fallback_key_start' + return true + } + return fail(char, state) + case 'filter_behavior_fallback_key_start': + if (char === ' ') { + return true + } + if ((char >= '0' && char <= '9') || (char >= 'A' && char <= 'Z')) { - lastRule.behaviors.push(lastBehavior = { - type: 'fallback' - , key: char - }) - state = 'filter_behavior_fallback_key_continued' - return true - } - return fail(char, state) - case 'filter_behavior_fallback_key_continued': - if (char === ' ') { - state = 'filter_behavior_start' - return true - } - if (char === '\n') { - state = 'filter_name_start' - return true - } - if ((char >= '0' && char <= '9') || + lastRule.behaviors.push(lastBehavior = { + type: 'fallback' + , key: char + }) + state = 'filter_behavior_fallback_key_continued' + return true + } + return fail(char, state) + case 'filter_behavior_fallback_key_continued': + if (char === ' ') { + state = 'filter_behavior_start' + return true + } + if (char === '\n') { + state = 'filter_name_start' + return true + } + if ((char >= '0' && char <= '9') || (char >= 'A' && char <= 'Z') || (char === '_')) { - lastBehavior.key += char - return true - } - return fail(char, state) - case 'filter_behavior_none_2': - if (char === 'o') { - state = 'filter_behavior_none_3' - return true - } - return fail(char, state) - case 'filter_behavior_none_3': - if (char === 'n') { - state = 'filter_behavior_none_4' - return true - } - return fail(char, state) - case 'filter_behavior_none_4': - if (char === 'e') { - lastRule.behaviors.push({ - type: 'none' - }) - state = 'filter_behavior_start' - return true - } - return fail(char, state) - case 'filter_behavior_literal_unicode_1': - if ((char >= '0' && char <= '9') || + lastBehavior.key += char + return true + } + return fail(char, state) + case 'filter_behavior_none_2': + if (char === 'o') { + state = 'filter_behavior_none_3' + return true + } + return fail(char, state) + case 'filter_behavior_none_3': + if (char === 'n') { + state = 'filter_behavior_none_4' + return true + } + return fail(char, state) + case 'filter_behavior_none_4': + if (char === 'e') { + lastRule.behaviors.push({ + type: 'none' + }) + state = 'filter_behavior_start' + return true + } + return fail(char, state) + case 'filter_behavior_literal_unicode_1': + if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'f')) { - lastRule.behaviors.push(lastBehavior = { - type: 'literal' - , value: parseInt(char, 16) << 12 - }) - state = 'filter_behavior_literal_unicode_2' - return true - } - return fail(char, state) - case 'filter_behavior_literal_unicode_2': - if ((char >= '0' && char <= '9') || + lastRule.behaviors.push(lastBehavior = { + type: 'literal' + , value: parseInt(char, 16) << 12 + }) + state = 'filter_behavior_literal_unicode_2' + return true + } + return fail(char, state) + case 'filter_behavior_literal_unicode_2': + if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'f')) { - lastBehavior.value += parseInt(char, 16) << 8 - state = 'filter_behavior_literal_unicode_3' - return true - } - return fail(char, state) - case 'filter_behavior_literal_unicode_3': - if ((char >= '0' && char <= '9') || + lastBehavior.value += parseInt(char, 16) << 8 + state = 'filter_behavior_literal_unicode_3' + return true + } + return fail(char, state) + case 'filter_behavior_literal_unicode_3': + if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'f')) { - lastBehavior.value += parseInt(char, 16) << 4 - state = 'filter_behavior_literal_unicode_4' - return true - } - return fail(char, state) - case 'filter_behavior_literal_unicode_4': - if ((char >= '0' && char <= '9') || + lastBehavior.value += parseInt(char, 16) << 4 + state = 'filter_behavior_literal_unicode_4' + return true + } + return fail(char, state) + case 'filter_behavior_literal_unicode_4': + if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'f')) { - lastBehavior.value += parseInt(char, 16) - lastBehavior.value = String.fromCharCode(lastBehavior.value) - state = 'filter_behavior_literal_end' - return true - } - return fail(char, state) - default: - throw new Error(util.format('Unexpected state "%s"', state)) + lastBehavior.value += parseInt(char, 16) + lastBehavior.value = String.fromCharCode(lastBehavior.value) + state = 'filter_behavior_literal_end' + return true + } + return fail(char, state) + default: + throw new Error(util.format('Unexpected state "%s"', state)) } } function errorListener(err) { @@ -458,66 +458,66 @@ keyutil.buildCharMap = function(keymap) { } var shouldHandle = rule.modifiers.every(function(modifier) { switch (modifier.type) { - case 'label': - return false // ignore - case 'base': - return true - case 'shift': - case 'lshift': - combination.modifiers.push(adb.KeyCodes.KEYCODE_SHIFT_LEFT) - combination.complexity += 10 - return true - case 'rshift': - combination.modifiers.push(adb.KeyCodes.KEYCODE_SHIFT_RIGHT) - combination.complexity += 10 - return true - case 'alt': - case 'lalt': - combination.modifiers.push(adb.KeyCodes.KEYCODE_ALT_LEFT) - combination.complexity += 20 - return true - case 'ralt': - combination.modifiers.push(adb.KeyCodes.KEYCODE_ALT_RIGHT) - combination.complexity += 20 - return true - case 'ctrl': - case 'lctrl': - combination.modifiers.push(adb.KeyCodes.KEYCODE_CTRL_LEFT) - combination.complexity += 20 - return true - case 'rctrl': - combination.modifiers.push(adb.KeyCodes.KEYCODE_CTRL_RIGHT) - combination.complexity += 20 - return true - case 'meta': - case 'lmeta': - combination.modifiers.push(adb.KeyCodes.KEYCODE_META_LEFT) - combination.complexity += 20 - return true - case 'rmeta': - combination.modifiers.push(adb.KeyCodes.KEYCODE_META_RIGHT) - combination.complexity += 20 - return true - case 'sym': - combination.modifiers.push(adb.KeyCodes.KEYCODE_SYM) - combination.complexity += 10 - return true - case 'fn': - combination.modifiers.push(adb.KeyCodes.KEYCODE_FUNCTION) - combination.complexity += 30 - return true - case 'capslock': - combination.modifiers.push(adb.KeyCodes.KEYCODE_CAPS_LOCK) - combination.complexity += 30 - return true - case 'numlock': - combination.modifiers.push(adb.KeyCodes.KEYCODE_NUM_LOCK) - combination.complexity += 30 - return true - case 'scrolllock': - combination.modifiers.push(adb.KeyCodes.KEYCODE_SCROLL_LOCK) - combination.complexity += 30 - return true + case 'label': + return false // ignore + case 'base': + return true + case 'shift': + case 'lshift': + combination.modifiers.push(adb.KeyCodes.KEYCODE_SHIFT_LEFT) + combination.complexity += 10 + return true + case 'rshift': + combination.modifiers.push(adb.KeyCodes.KEYCODE_SHIFT_RIGHT) + combination.complexity += 10 + return true + case 'alt': + case 'lalt': + combination.modifiers.push(adb.KeyCodes.KEYCODE_ALT_LEFT) + combination.complexity += 20 + return true + case 'ralt': + combination.modifiers.push(adb.KeyCodes.KEYCODE_ALT_RIGHT) + combination.complexity += 20 + return true + case 'ctrl': + case 'lctrl': + combination.modifiers.push(adb.KeyCodes.KEYCODE_CTRL_LEFT) + combination.complexity += 20 + return true + case 'rctrl': + combination.modifiers.push(adb.KeyCodes.KEYCODE_CTRL_RIGHT) + combination.complexity += 20 + return true + case 'meta': + case 'lmeta': + combination.modifiers.push(adb.KeyCodes.KEYCODE_META_LEFT) + combination.complexity += 20 + return true + case 'rmeta': + combination.modifiers.push(adb.KeyCodes.KEYCODE_META_RIGHT) + combination.complexity += 20 + return true + case 'sym': + combination.modifiers.push(adb.KeyCodes.KEYCODE_SYM) + combination.complexity += 10 + return true + case 'fn': + combination.modifiers.push(adb.KeyCodes.KEYCODE_FUNCTION) + combination.complexity += 30 + return true + case 'capslock': + combination.modifiers.push(adb.KeyCodes.KEYCODE_CAPS_LOCK) + combination.complexity += 30 + return true + case 'numlock': + combination.modifiers.push(adb.KeyCodes.KEYCODE_NUM_LOCK) + combination.complexity += 30 + return true + case 'scrolllock': + combination.modifiers.push(adb.KeyCodes.KEYCODE_SCROLL_LOCK) + combination.complexity += 30 + return true } }) if (!shouldHandle) { @@ -525,19 +525,19 @@ keyutil.buildCharMap = function(keymap) { } rule.behaviors.forEach(function(behavior) { switch (behavior.type) { - case 'literal': - if (!charmap[behavior.value]) { - charmap[behavior.value] = [combination] - } - else { - charmap[behavior.value].push(combination) - // Could be more efficient, but we only have 1-4 combinations - // per key, so we don't really care. - charmap[behavior.value].sort(function(a, b) { - return a.complexity - b.complexity - }) - } - break + case 'literal': + if (!charmap[behavior.value]) { + charmap[behavior.value] = [combination] + } + else { + charmap[behavior.value].push(combination) + // Could be more efficient, but we only have 1-4 combinations + // per key, so we don't really care. + charmap[behavior.value].sort(function(a, b) { + return a.complexity - b.complexity + }) + } + break } }) }) diff --git a/lib/util/ldaputil.js b/lib/util/ldaputil.js index 1c57aed08b..a450e0205f 100644 --- a/lib/util/ldaputil.js +++ b/lib/util/ldaputil.js @@ -91,11 +91,11 @@ export const login = function(options, username, password) { return tryConnect().then(function(client) { return tryFind(client) .then(function(entry) { - return tryBind(client, entry) - }) + return tryBind(client, entry) + }) .finally(function() { - client.unbind() - }) + client.unbind() + }) }) } export const email = function(user) { diff --git a/lib/util/lockutil.js b/lib/util/lockutil.js index d57373e9fc..edc1091c71 100644 --- a/lib/util/lockutil.js +++ b/lib/util/lockutil.js @@ -1,55 +1,65 @@ -import apiutil from './apiutil.js' -import dbapi from '../db/api.mjs' -const lockutil = Object.create(null) -lockutil.unlockDevice = function(lock) { +import * as apiutil from './apiutil.js' +import * as dbapi from '../db/api.js' + +export const unlockDevice = function(lock) { if (lock.device) { dbapi.unlockDevice(lock.device.serial) } } -lockutil.lockUser = function(email, res, lock) { +export const lockUser = function(email, res, lock) { return dbapi.lockUser(email) .then(function(stats) { - return apiutil.computeStats(res, stats, 'user', lock) - }) + return apiutil.computeStats(res, stats, 'user', lock) + }) } -lockutil.unlockUser = function(lock) { +export const unlockUser = function(lock) { if (lock.user) { dbapi.unlockUser(lock.user.email) } } -lockutil.lockGroupAndUser = function(req, res, lock) { - return lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) { +export const lockGroupAndUser = function(req, res, lock) { + return lockGroup(req, res, lock).then(function(lockingSuccessed) { return lockingSuccessed ? - lockutil.lockUser(req.user.email, res, lock) : + lockUser(req.user.email, res, lock) : false }) } -lockutil.unlockGroupAndUser = function(lock) { - lockutil.unlockGroup(lock) - lockutil.unlockUser(lock) +export const unlockGroupAndUser = function(lock) { + unlockGroup(lock) + unlockUser(lock) } -lockutil.lockGroup = function(req, res, lock) { +export const lockGroup = function(req, res, lock) { const id = req.params.id const email = req.user.email return dbapi.lockGroupByOwner(email, id).then(function(stats) { return apiutil.computeStats(res, stats, 'group', lock) }) } -lockutil.unlockGroup = function(lock) { +export const unlockGroup = function(lock) { if (lock.group) { dbapi.unlockGroup(lock.group.id) } } -lockutil.unlockGroupAndDevice = function(lock) { - lockutil.unlockGroup(lock) - lockutil.unlockDevice(lock) +export const unlockGroupAndDevice = function(lock) { + unlockGroup(lock) + unlockDevice(lock) } -lockutil.lockGenericDevice = function(req, res, lock, lockDevice) { +export const lockGenericDevice = function(req, res, lock, lockDevice) { return lockDevice(req.user.groups.subscribed, // eslint-disable-next-line no-prototype-builtins - req.hasOwnProperty('body') ? req.body.serial : req.query.serial) + req.hasOwnProperty('body') ? req.body.serial : req.query.serial) .then(function(stats) { - return apiutil.computeStats(res, stats, 'device', lock) - }) + return apiutil.computeStats(res, stats, 'device', lock) + }) +} +export default { + unlockDevice + , lockUser + , unlockUser + , lockGroupAndUser + , unlockGroupAndUser + , lockGroup + , unlockGroup + , unlockGroupAndDevice + , lockGenericDevice } -export default lockutil diff --git a/lib/util/logger.js b/lib/util/logger.js index 12c1af5413..230029b7ce 100644 --- a/lib/util/logger.js +++ b/lib/util/logger.js @@ -3,28 +3,16 @@ import events from 'events' import chalk from 'chalk' var Logger = new events.EventEmitter() Logger.Level = { - DEBUG: 1 - , VERBOSE: 2 - , INFO: 3 - , IMPORTANT: 4 - , WARNING: 5 - , ERROR: 6 - , FATAL: 7, + DEBUG: 1 + , VERBOSE: 2 + , INFO: 3 + , IMPORTANT: 4 + , WARNING: 5 + , ERROR: 6 + , FATAL: 7, } // Exposed for other modules Logger.LevelLabel = { - '1': 'DBG' - , '2': 'VRB' - , '3': 'INF' - , '4': 'IMP' - , '5': 'WRN' - , '6': 'ERR' - , '7': 'FTL', -} -Logger.globalIdentifier = '*' -function Log(tag) { - this.tag = tag - this.names = { '1': 'DBG' , '2': 'VRB' , '3': 'INF' @@ -32,89 +20,100 @@ function Log(tag) { , '5': 'WRN' , '6': 'ERR' , '7': 'FTL', - } - this.styles = { - '1': 'grey' - , '2': 'cyan' - , '3': 'green' - , '4': 'magenta' - , '5': 'yellow' - , '6': 'red' - , '7': 'red', - } - this.localIdentifier = null - events.EventEmitter.call(this) +} +Logger.globalIdentifier = '*' +function Log(tag) { + this.tag = tag + this.names = { + '1': 'DBG' + , '2': 'VRB' + , '3': 'INF' + , '4': 'IMP' + , '5': 'WRN' + , '6': 'ERR' + , '7': 'FTL', + } + this.styles = { + '1': 'grey' + , '2': 'cyan' + , '3': 'green' + , '4': 'magenta' + , '5': 'yellow' + , '6': 'red' + , '7': 'red', + } + this.localIdentifier = null + events.EventEmitter.call(this) } util.inherits(Log, events.EventEmitter) Logger.createLogger = function(tag) { - return new Log(tag) + return new Log(tag) } Logger.setGlobalIdentifier = function(identifier) { - Logger.globalIdentifier = identifier - return Logger + Logger.globalIdentifier = identifier + return Logger } Log.Entry = function(timestamp, priority, tag, pid, identifier, message) { - this.timestamp = timestamp - this.priority = priority - this.tag = tag - this.pid = pid - this.identifier = identifier - this.message = message + this.timestamp = timestamp + this.priority = priority + this.tag = tag + this.pid = pid + this.identifier = identifier + this.message = message } Log.prototype.setLocalIdentifier = function(identifier) { - this.localIdentifier = identifier + this.localIdentifier = identifier } Log.prototype.debug = function() { - this._write(this._entry(Logger.Level.DEBUG, arguments)) + this._write(this._entry(Logger.Level.DEBUG, arguments)) } Log.prototype.verbose = function() { - this._write(this._entry(Logger.Level.VERBOSE, arguments)) + this._write(this._entry(Logger.Level.VERBOSE, arguments)) } Log.prototype.info = function() { - this._write(this._entry(Logger.Level.INFO, arguments)) + this._write(this._entry(Logger.Level.INFO, arguments)) } Log.prototype.important = function() { - this._write(this._entry(Logger.Level.IMPORTANT, arguments)) + this._write(this._entry(Logger.Level.IMPORTANT, arguments)) } Log.prototype.warn = function() { - this._write(this._entry(Logger.Level.WARNING, arguments)) + this._write(this._entry(Logger.Level.WARNING, arguments)) } Log.prototype.error = function() { - this._write(this._entry(Logger.Level.ERROR, arguments)) + this._write(this._entry(Logger.Level.ERROR, arguments)) } Log.prototype.fatal = function() { - this._write(this._entry(Logger.Level.FATAL, arguments)) + this._write(this._entry(Logger.Level.FATAL, arguments)) } Log.prototype._entry = function(priority, args) { - return new Log.Entry( - new Date(), - priority, - this.tag, - process.pid, - this.localIdentifier || Logger.globalIdentifier, - util.format.apply(util, args) - ) + return new Log.Entry( + new Date(), + priority, + this.tag, + process.pid, + this.localIdentifier || Logger.globalIdentifier, + util.format.apply(util, args) + ) } Log.prototype._format = function(entry) { - // eslint-disable-next-line max-len - return util.format( - '%s %s/%s %d [%s] %s', - chalk.grey(entry.timestamp.toJSON()), - this._name(entry.priority), - chalk.bold(entry.tag), - entry.pid, - entry.identifier, - entry.message - ) + return util.format( + '%s %s/%s %d [%s] %s', + chalk.grey(entry.timestamp.toJSON()), + this._name(entry.priority), + chalk.bold(entry.tag), + entry.pid, + entry.identifier, + entry.message + ) } Log.prototype._name = function(priority) { - return chalk[this.styles[priority]](this.names[priority]) + return chalk[this.styles[priority]](this.names[priority]) } /* eslint no-console: 0 */ Log.prototype._write = function(entry) { - console.error(this._format(entry)) - this.emit('entry', entry) - Logger.emit('entry', entry) + console.error(this._format(entry)) + this.emit('entry', entry) + Logger.emit('entry', entry) } export default Logger diff --git a/lib/util/pathutil.cjs b/lib/util/pathutil.cjs index 79c4a687d2..a7428e7630 100644 --- a/lib/util/pathutil.cjs +++ b/lib/util/pathutil.cjs @@ -12,6 +12,11 @@ module.exports.resource = function(target) { return path.resolve(__dirname, '../../res', target) } +// Export +module.exports.reactFrontend = function(target) { + return path.resolve(__dirname, '../../ui', target) +} + // Export module.exports.vendor = function(target) { return path.resolve(__dirname, '../../vendor', target) diff --git a/lib/util/procutil.js b/lib/util/procutil.js index 11352a1c68..e8a4b22460 100644 --- a/lib/util/procutil.js +++ b/lib/util/procutil.js @@ -40,17 +40,17 @@ export const fork = function(filename, args) { }) return resolver.promise.cancellable() .finally(function() { - process.removeListener('SIGINT', sigintListener) - process.removeListener('SIGTERM', sigtermListener) - }) + process.removeListener('SIGINT', sigintListener) + process.removeListener('SIGTERM', sigtermListener) + }) .catch(Promise.CancellationError, function() { - return new Promise(function(resolve) { - proc.on('exit', function() { - resolve() + return new Promise(function(resolve) { + proc.on('exit', function() { + resolve() + }) + proc.kill() }) - proc.kill() }) - }) } export const gracefullyKill = function(proc, timeout) { function killer(signal) { @@ -67,8 +67,8 @@ export const gracefullyKill = function(proc, timeout) { return killer('SIGTERM') .timeout(timeout) .catch(function() { - return killer('SIGKILL') - .timeout(timeout) - }) + return killer('SIGKILL') + .timeout(timeout) + }) } export {ExitError} diff --git a/lib/util/riskystream.js b/lib/util/riskystream.js index 4b84b88562..20b57a9001 100644 --- a/lib/util/riskystream.js +++ b/lib/util/riskystream.js @@ -40,7 +40,7 @@ RiskyStream.prototype.waitForEnd = function() { stream.resume() }) .finally(function() { - stream.removeListener('end', endListener) - }) + stream.removeListener('end', endListener) + }) } export default RiskyStream diff --git a/lib/util/serviceuser.js b/lib/util/serviceuser.js index a366c61d88..32ba6d6230 100644 --- a/lib/util/serviceuser.js +++ b/lib/util/serviceuser.js @@ -1,4 +1,4 @@ -import dbapi from '../db/api.mjs' +import * as dbapi from '../db/api.js' import * as jwtutil from './jwtutil.js' import util from 'util' import uuid from 'uuid' diff --git a/lib/util/srv.js b/lib/util/srv.js index fa8c655820..fc4263986e 100644 --- a/lib/util/srv.js +++ b/lib/util/srv.js @@ -72,21 +72,21 @@ srv.resolve = function(domain) { return dns.resolveSrvAsync(parsedUrl.hostname) .then(module.exports.sort) .then(function(records) { - return records.map(function(record) { - parsedUrl.host = util.format('%s:%d', record.name, record.port) - parsedUrl.hostname = record.name - parsedUrl.port = record.port - record.url = url.format(parsedUrl) - return record + return records.map(function(record) { + parsedUrl.host = util.format('%s:%d', record.name, record.port) + parsedUrl.hostname = record.name + parsedUrl.port = record.port + record.url = url.format(parsedUrl) + return record + }) }) - }) } else { return Promise.resolve([{ - url: domain - , name: parsedUrl.hostname - , port: parsedUrl.port - }]) + url: domain + , name: parsedUrl.hostname + , port: parsedUrl.port + }]) } } srv.attempt = function(records, fn) { diff --git a/lib/util/streamutil.js b/lib/util/streamutil.js index ff2a58ec60..a4e2bd795f 100644 --- a/lib/util/streamutil.js +++ b/lib/util/streamutil.js @@ -59,10 +59,10 @@ export const findLine = function(stream, re) { export const talk = function(log, format, stream) { stream.pipe(split()) .on('data', function(chunk) { - var line = chunk.toString().trim() - if (line.length) { - log.info(format, line) - } - }) + var line = chunk.toString().trim() + if (line.length) { + log.info(format, line) + } + }) } export {NoSuchLineError} diff --git a/lib/util/timeutil.js b/lib/util/timeutil.js index e8cb6528aa..0d4dde7f36 100644 --- a/lib/util/timeutil.js +++ b/lib/util/timeutil.js @@ -5,14 +5,14 @@ const timeutil = Object.create(null) timeutil.now = function(unit) { const hrTime = process.hrtime() switch (unit) { - case 'milli': - return hrTime[0] * 1000 + hrTime[1] / 1000000 - case 'micro': - return hrTime[0] * 1000000 + hrTime[1] / 1000 - case 'nano': - return hrTime[0] * 1000000000 + hrTime[1] - default: - return hrTime[0] * 1000000000 + hrTime[1] + case 'milli': + return hrTime[0] * 1000 + hrTime[1] / 1000000 + case 'micro': + return hrTime[0] * 1000000 + hrTime[1] / 1000 + case 'nano': + return hrTime[0] * 1000000000 + hrTime[1] + default: + return hrTime[0] * 1000000000 + hrTime[1] } } export default timeutil diff --git a/lib/util/urlutil.js b/lib/util/urlutil.js index 9941867fa8..f7b6280aee 100644 --- a/lib/util/urlutil.js +++ b/lib/util/urlutil.js @@ -3,7 +3,7 @@ export const addParams = function(originalUrl, params) { var parsed = url.parse(originalUrl, true) parsed.search = null for (const [key, value] of Object.entries(params)) { - parsed.query[key] = value + parsed.query[key] = value } return url.format(parsed) } diff --git a/lib/util/zmqutil.js b/lib/util/zmqutil.js index 5f31b38055..cfc545d1d7 100644 --- a/lib/util/zmqutil.js +++ b/lib/util/zmqutil.js @@ -13,18 +13,18 @@ import logger from './logger.js' const log = logger.createLogger('util:zmqutil') export const socket = function() { - let sock = zmq.socket.apply(zmq, arguments) + let sock = zmq.socket.apply(zmq, arguments) ;['ZMQ_TCP_KEEPALIVE', 'ZMQ_TCP_KEEPALIVE_IDLE', 'ZMQ_IPV6'].forEach(function(opt) { - if (process.env[opt]) { - try { - sock.setsockopt(zmq[opt], Number(process.env[opt])) - } - catch (err) { - log.warn('ZeroMQ library too old, no support for %s', opt) - } - } - }) + if (process.env[opt]) { + try { + sock.setsockopt(zmq[opt], Number(process.env[opt])) + } + catch (err) { + log.warn('ZeroMQ library too old, no support for %s', opt) + } + } + }) - return sock + return sock } diff --git a/lib/wire/index.js b/lib/wire/index.js index 4b203d2a37..fdfa458b9d 100644 --- a/lib/wire/index.js +++ b/lib/wire/index.js @@ -3,12 +3,12 @@ import ProtoBuf from 'protobufjs' var wire = ProtoBuf.loadProtoFile(path.join(import.meta.dirname, 'wire.proto')).build() wire.ReverseMessageType = Object.keys(wire.MessageType) .reduce(function(acc, type) { - var code = wire.MessageType[type] - if (!wire[type]) { - throw new Error('wire.MessageType has unknown value "' + type + '"') - } - wire[type].$code = wire[type].prototype.$code = code - acc[code] = type - return acc -}, Object.create(null)) + var code = wire.MessageType[type] + if (!wire[type]) { + throw new Error('wire.MessageType has unknown value "' + type + '"') + } + wire[type].$code = wire[type].prototype.$code = code + acc[code] = type + return acc + }, Object.create(null)) export default wire diff --git a/lib/wire/router.js b/lib/wire/router.js index e4dc96de04..992c8aa85c 100644 --- a/lib/wire/router.js +++ b/lib/wire/router.js @@ -29,7 +29,7 @@ Router.prototype.handler = function() { log.error('Received message with type "%s", but cant parse data ' + wrapper.message) throw e } - log.info('Received message with type "%s", and data \n %s', type || wrapper.type, JSON.stringify(decodedMessage)) + log.info('Received message with type "%s", and data %s', type || wrapper.type, JSON.stringify(decodedMessage)) if (type) { this.emit(wrapper.type, wrapper.channel || channel, decodedMessage, data) this.emit('message', channel) diff --git a/lib/wire/util.js b/lib/wire/util.js index 54bfdb85c6..a1f371c4d5 100644 --- a/lib/wire/util.js +++ b/lib/wire/util.js @@ -42,14 +42,12 @@ var wireutil = { var seq = 0 return { okay: function(data, body) { - // eslint-disable-next-line max-len return wireutil.envelope(new wire.TransactionDoneMessage(source, seq++, true, data === null ? null : (data || 'success'), body ? JSON.stringify(body) : null)) } , fail: function(data, body) { return wireutil.envelope(new wire.TransactionDoneMessage(source, seq++, false, data || 'fail', body ? JSON.stringify(body) : null)) } , tree: function(data, body) { - // eslint-disable-next-line max-len return wireutil.envelope(new wire.TransactionTreeMessage(source, seq++, true, data === null ? null : (data || 'success'), body ? JSON.stringify(body) : null)) } , progress: function(data, progress) { diff --git a/lib/wire/wire.proto b/lib/wire/wire.proto index 17b2e6bb4c..6150df5531 100644 --- a/lib/wire/wire.proto +++ b/lib/wire/wire.proto @@ -1,5 +1,5 @@ // -// Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0 +// Copyright © 2024 contains code contributed by V Kontakte LLC, authors: Daniil Smirnov, Egor Platonov, Aleksey Chistov - Licensed under the Apache license 2.0 // // Message wrapper @@ -87,25 +87,19 @@ enum MessageType { Applications = 85; ConnectStartedMessage = 92; ConnectStoppedMessage = 93; - SetIosDeviceDisplay = 94; + SetDeviceDisplay = 94; IosDevicePorts = 95; StartStreaming = 96; - TestPush = 97; - TouchDownIosMessage = 98; TouchMoveIosMessage = 99; DeviceIosIntroductionMessage = 100; ProviderIosMessage = 101; - CheckIosDeviceConnected = 102; - ConnectDeviceViaUSB = 103; - DeleteIosDevice = 104; + DeleteDevice = 104; SetAbsentDisconnectedDevices = 105; UninstallIosMessage = 106; SetDeviceApp = 107; - RotationIosEvent = 108; GetIosDeviceApps = 109; TransationGetMessage = 110; UpdateIosDevice = 112; - BatteryIosEvent = 113; SdkIosVersion = 114; SizeIosDevice = 115; DashboardOpenMessage = 116; @@ -397,7 +391,7 @@ message DeviceIntroductionMessage { required string serial = 1; required DeviceStatus status = 2; required ProviderMessage provider = 3; - optional DeviceGroupMessage group = 4; +// optional DeviceGroupMessage group = 4; } message DeviceIosIntroductionMessage { @@ -597,10 +591,6 @@ message LeaveGroupMessage { message PhysicalIdentifyMessage { } -message TouchDownIosMessage { - required float x = 1; - required float y = 2; -} message TouchDownMessage { required uint32 seq = 1; @@ -865,7 +855,7 @@ message BatteryEvent { required uint32 level = 5; required uint32 scale = 6; required double temp = 7; - required double voltage = 8; + optional double voltage = 8; } message ConnectivityEvent { @@ -887,16 +877,11 @@ message PhoneStateEvent { message RotationEvent { required string serial = 1; required int32 rotation = 2; + optional int32 width = 3; + optional int32 height = 4; } -message RotationIosEvent { - required string serial = 1; - required int32 rotation = 2; - required int32 width = 3; - required int32 height = 4; -} - -message SetIosDeviceDisplay { +message SetDeviceDisplay { required string serial = 1; required string channel = 2; required int32 width = 3; @@ -910,17 +895,7 @@ message StartStreaming { required int32 port = 1; required string channel = 2; } -message TestPush { - required string action = 1; -} -message CheckIosDeviceConnected { - required string id = 1; - required string channel = 2; -} -message ConnectDeviceViaUSB { - required string id = 1; -} -message DeleteIosDevice { +message DeleteDevice { required string serial = 1; } message SetAbsentDisconnectedDevices { @@ -965,15 +940,7 @@ message UpdateIosDevice{ required string platform = 3; required string architect = 4; } -message BatteryIosEvent{ - required string id = 1; - required string health = 2; - required string source = 3; - required string status = 4; - required int32 level = 5; - required string temp = 6; - required int32 scale = 7; -} + message SdkIosVersion{ required string id = 1; required string device = 2; diff --git a/package-lock.json b/package-lock.json index ec3b0ebc0c..cb86f77a16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vk-devicehub", - "version": "1.3.4", + "version": "1.3.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vk-devicehub", - "version": "1.3.4", + "version": "1.3.5", "dependencies": { "@devicefarmer/adbkit": "3.2.6", "@devicefarmer/adbkit-apkreader": "3.2.4", @@ -19,6 +19,7 @@ "@devicefarmer/stf-syrup": "1.0.2", "@devicefarmer/stf-wiki": "1.0.0", "@julusian/jpeg-turbo": "2.1.0", + "@sentry/node": "^8.34.0", "android-device-list": "1.2.10", "angular": "1.8.3", "angular-animate": "1.8.3", @@ -42,8 +43,10 @@ "chalk": "1.1.3", "components-font-awesome": "4.7.0", "compression": "1.7.4", + "cookie": "^1.0.1", "cookie-parser": "^1.4.6", "cookie-session": "2.0.0", + "cors": "^2.8.5", "csurf": "1.11.0", "d3": "3.5.17", "debug": "4.3.4", @@ -123,9 +126,11 @@ "zeromq": "^5.0.0" }, "bin": { - "stf": "bin/stf" + "stf": "bin/stf.mjs" }, "devDependencies": { + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "^9.13.0", "angular-mocks": "1.8.3", "async": "2.6.4", "chai": "3.5.0", @@ -136,10 +141,11 @@ "event-stream": "3.3.5", "exports-loader": "^4.0.0", "fs-extra": "8.1.0", + "globals": "^15.11.0", "gulp": "4.0.2", "gulp-angular-gettext": "2.3.0", - "gulp-eslint": "6.0.0", - "gulp-eslint-if-fixed": "^1.0.0", + "gulp-eslint-new": "^2.3.0", + "gulp-if": "^3.0.0", "gulp-jsonlint": "1.3.2", "gulp-protractor": "3.0.0", "gulp-pug": "4.0.1", @@ -173,10 +179,11 @@ "style-loader": "^3.3.3", "template-html-loader": "0.0.4", "then-jade": "2.4.4", + "typescript": "^5.5.3", "webpack-dev-server": "^4.15.1" }, "engines": { - "node": ">= 12", + "node": ">= 20", "npm": ">=8.5.2" } }, @@ -409,15 +416,16 @@ "integrity": "sha512-p87QMgXmJS5K8lWkVvYgwmSHmxSsB+N8ctcZZsGx26wOh+0/RiDfsVQAIMv+SME6IWVXIjo/FiZRsGNlvw85Sg==" }, "node_modules/@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -425,12 +433,79 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", + "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.10.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.7.tgz", @@ -667,6 +742,1010 @@ "node": ">= 8" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.54.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.54.2.tgz", + "integrity": "sha512-4MTVwwmLgUh5QrJnZpYo6YRO5IBLAggf2h8gWDblwRagDStY13aEvt7gGk3jewrMaPlHiF83fENhIx0HO97/cQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.28.0.tgz", + "integrity": "sha512-igcl4Ve+F1N2063PJUkesk/GkYyuGIWinYkSyAFTnIj3gzrOgvOA4k747XNdL47HRRL1w/qh7UW8NDuxOLvKFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.28.0.tgz", + "integrity": "sha512-ZLwRMV+fNDpVmF2WYUdBHlq0eOWtEaUJSusrzjGnBt7iSRvfjFE3RXYUZJrqou/wIDWV0DwQ5KIfYe9WXg9Xqw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.54.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.54.2.tgz", + "integrity": "sha512-go6zpOVoZVztT9r1aPd79Fr3OWiD4N24bCPJsIKkBses8oyFo12F/Ew3UBTdIu6hsW4HC4MVEJygG6TEyJI/lg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.54.2", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-amqplib": { + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.43.0.tgz", + "integrity": "sha512-ALjfQC+0dnIEcvNYsbZl/VLh7D2P1HhFF4vicRKHhHFIUV3Shpg4kXgiek5PLhmeKSIPiUB25IYH5RIneclL4A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-connect": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.40.0.tgz", + "integrity": "sha512-3aR/3YBQ160siitwwRLjwqrv2KBT16897+bo6yz8wIfel6nWOxTZBJudcbsK3p42pTC7qrbotJ9t/1wRLpv79Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.36" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-connect/node_modules/@types/connect": { + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", + "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@opentelemetry/instrumentation-dataloader": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.12.0.tgz", + "integrity": "sha512-pnPxatoFE0OXIZDQhL2okF//dmbiWFzcSc8pUg9TqofCLYZySSxDCgQc69CJBo5JnI3Gz1KP+mOjS4WAeRIH4g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.53.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-dataloader/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-dataloader/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-express": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.44.0.tgz", + "integrity": "sha512-GWgibp6Q0wxyFaaU8ERIgMMYgzcHmGrw3ILUtGchLtLncHNOKk0SNoWGqiylXWWT4HTn5XdV8MGawUgpZh80cA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-fastify": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.41.0.tgz", + "integrity": "sha512-pNRjFvf0mvqfJueaeL/qEkuGJwgtE5pgjIHGYwjc2rMViNCrtY9/Sf+Nu8ww6dDd/Oyk2fwZZP7i0XZfCnETrA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-fs": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.16.0.tgz", + "integrity": "sha512-hMDRUxV38ln1R3lNz6osj3YjlO32ykbHqVrzG7gEhGXFQfu7LJUx8t9tEwE4r2h3CD4D0Rw4YGDU4yF4mP3ilg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.54.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-generic-pool": { + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.39.0.tgz", + "integrity": "sha512-y4v8Y+tSfRB3NNBvHjbjrn7rX/7sdARG7FuK6zR8PGb28CTa0kHpEGCJqvL9L8xkTNvTXo+lM36ajFGUaK1aNw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.53.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-generic-pool/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-generic-pool/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-graphql": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.44.0.tgz", + "integrity": "sha512-FYXTe3Bv96aNpYktqm86BFUTpjglKD0kWI5T5bxYkLUPEPvFn38vWGMJTGrDMVou/i55E4jlWvcm6hFIqLsMbg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.54.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-hapi": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.41.0.tgz", + "integrity": "sha512-jKDrxPNXDByPlYcMdZjNPYCvw0SQJjN+B1A+QH+sx+sAHsKSAf9hwFiJSrI6C4XdOls43V/f/fkp9ITkHhKFbQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-hapi/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-hapi/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.53.0.tgz", + "integrity": "sha512-H74ErMeDuZfj7KgYCTOFGWF5W9AfaPnqLQQxeFq85+D29wwV2yqHbz2IKLYpkOh7EI6QwDEl7rZCIxjJLyc/CQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.26.0", + "@opentelemetry/instrumentation": "0.53.0", + "@opentelemetry/semantic-conventions": "1.27.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.26.0.tgz", + "integrity": "sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-ioredis": { + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.43.0.tgz", + "integrity": "sha512-i3Dke/LdhZbiUAEImmRG3i7Dimm/BD7t8pDDzwepSvIQ6s2X6FPia7561gw+64w+nx0+G9X14D7rEfaMEmmjig==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-ioredis/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-ioredis/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-kafkajs": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.4.0.tgz", + "integrity": "sha512-I9VwDG314g7SDL4t8kD/7+1ytaDBRbZQjhVaQaVIDR8K+mlsoBhLsWH79yHxhHQKvwCSZwqXF+TiTOhoQVUt7A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-knex": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.41.0.tgz", + "integrity": "sha512-OhI1SlLv5qnsnm2dOVrian/x3431P75GngSpnR7c4fcVFv7prXGYu29Z6ILRWJf/NJt6fkbySmwdfUUnFnHCTg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-koa": { + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.43.0.tgz", + "integrity": "sha512-lDAhSnmoTIN6ELKmLJBplXzT/Jqs5jGZehuG22EdSMaTwgjMpxMDI1YtlKEhiWPWkrz5LUsd0aOO0ZRc9vn3AQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-koa/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-koa/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.40.0.tgz", + "integrity": "sha512-21xRwZsEdMPnROu/QsaOIODmzw59IYpGFmuC4aFWvMj6stA8+Ei1tX67nkarJttlNjoM94um0N4X26AD7ff54A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.53.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-lru-memoizer/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-lru-memoizer/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongodb": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.48.0.tgz", + "integrity": "sha512-9YWvaGvrrcrydMsYGLu0w+RgmosLMKe3kv/UNlsPy8RLnCkN2z+bhhbjjjuxtUmvEuKZMCoXFluABVuBr1yhjw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongoose": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.42.0.tgz", + "integrity": "sha512-AnWv+RaR86uG3qNEMwt3plKX1ueRM7AspfszJYVkvkehiicC3bHQA6vWdb6Zvy5HAE14RyFbu9+2hUUjR2NSyg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongoose/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-mongoose/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.41.0.tgz", + "integrity": "sha512-jnvrV6BsQWyHS2qb2fkfbfSb1R/lmYwqEZITwufuRl37apTopswu9izc0b1CYRp/34tUG/4k/V39PND6eyiNvw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/mysql": "2.15.26" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql2": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.41.0.tgz", + "integrity": "sha512-REQB0x+IzVTpoNgVmy5b+UnH1/mDByrneimP6sbDHkp1j8QOl1HyWOrBH/6YWR0nrbU3l825Em5PlybjT3232g==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@opentelemetry/sql-common": "^0.40.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql2/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql2/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-nestjs-core": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.40.0.tgz", + "integrity": "sha512-WF1hCUed07vKmf5BzEkL0wSPinqJgH7kGzOjjMAiTGacofNXjb/y4KQ8loj2sNsh5C/NN7s1zxQuCgbWbVTGKg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-nestjs-core/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-nestjs-core/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-pg": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.44.0.tgz", + "integrity": "sha512-oTWVyzKqXud1BYEGX1loo2o4k4vaU1elr3vPO8NZolrBtFvQ34nx4HgUaexUDuEog00qQt+MLR5gws/p+JXMLQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@opentelemetry/sql-common": "^0.40.1", + "@types/pg": "8.6.1", + "@types/pg-pool": "2.0.6" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-pg/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-pg/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-redis-4": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.42.0.tgz", + "integrity": "sha512-NaD+t2JNcOzX/Qa7kMy68JbmoVIV37fT/fJYzLKu2Wwd+0NCxt+K2OOsOakA8GVg8lSpFdbx4V/suzZZ2Pvdjg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-redis-4/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-redis-4/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-tedious": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.15.0.tgz", + "integrity": "sha512-Kb7yo8Zsq2TUwBbmwYgTAMPK0VbhoS8ikJ6Bup9KrDtCx2JC01nCb+M0VJWXt7tl0+5jARUbKWh5jRSoImxdCw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/tedious": "^4.0.14" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.6.0.tgz", + "integrity": "sha512-ABJBhm5OdhGmbh0S/fOTE4N69IZ00CsHC5ijMYfzbw3E5NwLgpQk5xsljaECrJ8wz1SfXbO03FiSuu5AyRAkvQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.53.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" + } + }, + "node_modules/@opentelemetry/instrumentation-undici/node_modules/@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/instrumentation-undici/node_modules/@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/redis-common": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz", + "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.28.0.tgz", + "integrity": "sha512-cIyXSVJjGeTICENN40YSvLDAq4Y2502hGK3iN7tfdynQLKWb3XWZQEkPc+eSx47kiy11YeFAlYkEfXwR1w8kfw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.28.0", + "@opentelemetry/semantic-conventions": "1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.28.0.tgz", + "integrity": "sha512-ceUVWuCpIao7Y5xE02Xs3nQi0tOGmMea17ecBdwtCvdo9ekmO+ijc9RFDgfifMl7XCBf41zne/1POM3LqSTZDA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "1.28.0", + "@opentelemetry/resources": "1.28.0", + "@opentelemetry/semantic-conventions": "1.27.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz", + "integrity": "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/sql-common": { + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz", + "integrity": "sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^1.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -730,11 +1809,157 @@ "node": "*" } }, + "node_modules/@prisma/instrumentation": { + "version": "5.19.1", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-5.19.1.tgz", + "integrity": "sha512-VLnzMQq7CWroL5AeaW0Py2huiNKeoMfCH3SUxstdzPrlWQi6UQ9UrfcbUkNHlVFqOMacqy8X/8YtE0kuKDpD9w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.8", + "@opentelemetry/instrumentation": "^0.49 || ^0.50 || ^0.51 || ^0.52.0", + "@opentelemetry/sdk-trace-base": "^1.22" + } + }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/api-logs": { + "version": "0.52.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz", + "integrity": "sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/instrumentation": { + "version": "0.52.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.52.1.tgz", + "integrity": "sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.52.1", + "@types/shimmer": "^1.0.2", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, "node_modules/@sailshq/lodash": { "version": "3.10.6", "resolved": "https://registry.npmjs.org/@sailshq/lodash/-/lodash-3.10.6.tgz", "integrity": "sha512-gp2pRE/kyh3DbrQ7MaI65xNcjrToZHRa52XJsNB8kZ0Aj0fcDNQPw8entkcuaaPYzCPxXau4rpOXaw/rl0c/ZQ==" }, + "node_modules/@sentry/core": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.38.0.tgz", + "integrity": "sha512-sGD+5TEHU9G7X7zpyaoJxpOtwjTjvOd1f/MKBrWW2vf9UbYK+GUJrOzLhMoSWp/pHSYgvObkJkDb/HwieQjvhQ==", + "license": "MIT", + "dependencies": { + "@sentry/types": "8.38.0", + "@sentry/utils": "8.38.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/node": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-8.38.0.tgz", + "integrity": "sha512-nwW0XqZFQseXYn0i6i6nKPkbjgHMBEFSF9TnK6mHHqJHHObHIZ6qu5CfvGKgxATia8JPIg9NN8XcyYOnQMi07w==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.25.1", + "@opentelemetry/core": "^1.25.1", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/instrumentation-amqplib": "^0.43.0", + "@opentelemetry/instrumentation-connect": "0.40.0", + "@opentelemetry/instrumentation-dataloader": "0.12.0", + "@opentelemetry/instrumentation-express": "0.44.0", + "@opentelemetry/instrumentation-fastify": "0.41.0", + "@opentelemetry/instrumentation-fs": "0.16.0", + "@opentelemetry/instrumentation-generic-pool": "0.39.0", + "@opentelemetry/instrumentation-graphql": "0.44.0", + "@opentelemetry/instrumentation-hapi": "0.41.0", + "@opentelemetry/instrumentation-http": "0.53.0", + "@opentelemetry/instrumentation-ioredis": "0.43.0", + "@opentelemetry/instrumentation-kafkajs": "0.4.0", + "@opentelemetry/instrumentation-knex": "0.41.0", + "@opentelemetry/instrumentation-koa": "0.43.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.40.0", + "@opentelemetry/instrumentation-mongodb": "0.48.0", + "@opentelemetry/instrumentation-mongoose": "0.42.0", + "@opentelemetry/instrumentation-mysql": "0.41.0", + "@opentelemetry/instrumentation-mysql2": "0.41.0", + "@opentelemetry/instrumentation-nestjs-core": "0.40.0", + "@opentelemetry/instrumentation-pg": "0.44.0", + "@opentelemetry/instrumentation-redis-4": "0.42.0", + "@opentelemetry/instrumentation-tedious": "0.15.0", + "@opentelemetry/instrumentation-undici": "0.6.0", + "@opentelemetry/resources": "^1.26.0", + "@opentelemetry/sdk-trace-base": "^1.26.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@prisma/instrumentation": "5.19.1", + "@sentry/core": "8.38.0", + "@sentry/opentelemetry": "8.38.0", + "@sentry/types": "8.38.0", + "@sentry/utils": "8.38.0", + "import-in-the-middle": "^1.11.2" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-8.38.0.tgz", + "integrity": "sha512-AfjmIf/v7+x2WplhkX66LyGKvrzzPeSgff9uJ0cFCC2s0yd1qA2VPuIwEyr5i/FOJOP5bvFr8tu/hz3LA4+F5Q==", + "license": "MIT", + "dependencies": { + "@sentry/core": "8.38.0", + "@sentry/types": "8.38.0", + "@sentry/utils": "8.38.0" + }, + "engines": { + "node": ">=14.18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^1.25.1", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/sdk-trace-base": "^1.26.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + } + }, + "node_modules/@sentry/types": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.38.0.tgz", + "integrity": "sha512-fP5H9ZX01W4Z/EYctk3mkSHi7d06cLcX2/UWqwdWbyPWI+pL2QpUPICeO/C+8SnmYx//wFj3qWDhyPCh1PdFAA==", + "license": "MIT", + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/utils": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.38.0.tgz", + "integrity": "sha512-3X7MgIKIx+2q5Al7QkhaRB4wV6DvzYsaeIwdqKUzGLuRjXmNgJrLoU87TAwQRmZ6Wr3IoEpThZZMNrzYPXxArw==", + "license": "MIT", + "dependencies": { + "@sentry/types": "8.38.0" + }, + "engines": { + "node": ">=14.18" + } + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -805,9 +2030,10 @@ } }, "node_modules/@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "license": "MIT", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -878,6 +2104,15 @@ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, + "node_modules/@types/mysql": { + "version": "2.15.26", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz", + "integrity": "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "20.14.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.6.tgz", @@ -900,6 +2135,26 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, + "node_modules/@types/pg": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", + "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/pg-pool": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", + "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", + "license": "MIT", + "dependencies": { + "@types/pg": "*" + } + }, "node_modules/@types/pug": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", @@ -962,6 +2217,12 @@ "@types/send": "*" } }, + "node_modules/@types/shimmer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", + "license": "MIT" + }, "node_modules/@types/sockjs": { "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", @@ -971,6 +2232,15 @@ "@types/node": "*" } }, + "node_modules/@types/tedious": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", + "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -1163,9 +2433,10 @@ } }, "node_modules/acorn": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", - "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1472,33 +2743,6 @@ "node": ">=0.10.0" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-gray": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", @@ -1986,15 +3230,6 @@ "node": ">= 0.8" } }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -2273,6 +3508,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bare-events": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", + "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, "node_modules/barrage": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/barrage/-/barrage-1.1.0.tgz", @@ -2427,6 +3670,110 @@ "file-uri-to-path": "1.0.0" } }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bl/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bl/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/blocking-proxy": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", @@ -2964,12 +4311,6 @@ "is-regex": "^1.0.3" } }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, "node_modules/cheerio": { "version": "0.22.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", @@ -3073,6 +4414,12 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "license": "MIT" + }, "node_modules/class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -3125,18 +4472,6 @@ "node": ">= 4.0" } }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/cli-docs-generator": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/cli-docs-generator/-/cli-docs-generator-1.0.7.tgz", @@ -3362,15 +4697,6 @@ "decamelize": "^1.2.0" } }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -3932,11 +5258,12 @@ "dev": true }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.1.tgz", + "integrity": "sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" } }, "node_modules/cookie-parser": { @@ -3951,6 +5278,15 @@ "node": ">= 0.8.0" } }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-session": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/cookie-session/-/cookie-session-2.0.0.tgz", @@ -5196,6 +6532,15 @@ "node": ">=10.0.0" } }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/engine.io/node_modules/engine.io-parser": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", @@ -5607,6 +6952,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -5686,6 +7055,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -5939,12 +7324,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/execa/node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -6227,32 +7606,6 @@ "node": ">=0.10.0" } }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/external-editor/node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -6317,6 +7670,13 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -6459,21 +7819,6 @@ "pend": "~1.2.0" } }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -6780,7 +8125,8 @@ "version": "0.0.4", "resolved": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz", "integrity": "sha512-Pqq5NnT78ehvUnAk/We/Jr22vSvanRlFTpAmQ88xBY/M1TlHe+P0ILuEyXS595ysdGfaj22634LBkGMA2GTcpA==", - "dev": true + "dev": true, + "license": "BSD" }, "node_modules/form-data": { "version": "4.0.0", @@ -7246,15 +8592,13 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", + "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7826,660 +9170,438 @@ "node": ">=8.6" } }, - "node_modules/grunt/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/grunt/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "peer": true - }, - "node_modules/grunt/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "peer": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", - "dev": true, - "dependencies": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-angular-gettext": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-angular-gettext/-/gulp-angular-gettext-2.3.0.tgz", - "integrity": "sha512-LzCPhxuL0wvBXN1CQxAN+yH70HFte0x8dSDPW4UZPSOX+SepEeF8Nfgl+1TTOdrrwSk2FOZ/6KKtYUcJee2MvQ==", - "dev": true, - "dependencies": { - "angular-gettext-tools": "^2.2.0", - "plugin-error": "^1.0.1", - "through2": "^2.0.1", - "vinyl": "^2.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", - "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", - "dev": true, - "dependencies": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.4.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.2.0", - "yargs": "^7.1.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-cli/node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gulp-cli/node_modules/yargs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", - "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", - "dev": true, - "dependencies": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.1" - } - }, - "node_modules/gulp-cli/node_modules/yargs-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", - "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", - "dev": true, - "dependencies": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" - } - }, - "node_modules/gulp-eslint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-6.0.0.tgz", - "integrity": "sha512-dCVPSh1sA+UVhn7JSQt7KEb4An2sQNbOdB3PA8UCfxsoPlAKjJHxYHGXdXC7eb+V1FAnilSFFqslPrq037l1ig==", - "dev": true, - "dependencies": { - "eslint": "^6.0.0", - "fancy-log": "^1.3.2", - "plugin-error": "^1.0.1" - } - }, - "node_modules/gulp-eslint-if-fixed": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulp-eslint-if-fixed/-/gulp-eslint-if-fixed-1.0.0.tgz", - "integrity": "sha512-Cx8SNxafW1sfkERGpprl6vduMsT/LEqhhhcnA6ieUzDUU8u6Tq8963GcqWhWPVYYxrME4rwalsYU7DmbkdFhLw==", - "dev": true, - "dependencies": { - "gulp-if": "^2.0.0" - } - }, - "node_modules/gulp-eslint/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/gulp-eslint/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/gulp-eslint/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-eslint/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/gulp-eslint/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-eslint/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/gulp-eslint/node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/gulp-eslint/node_modules/eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.3", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/gulp-eslint/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, + "node_modules/grunt/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "peer": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8.0.0" + "node": "*" } }, - "node_modules/gulp-eslint/node_modules/eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, + "node_modules/grunt/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "peer": true + }, + "node_modules/grunt/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "peer": true, "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "is-number": "^7.0.0" }, "engines": { - "node": ">=6" + "node": ">=8.0" } }, - "node_modules/gulp-eslint/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "node_modules/gulp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", "dev": true, + "dependencies": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, "engines": { - "node": ">=4" + "node": ">= 0.10" } }, - "node_modules/gulp-eslint/node_modules/espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "node_modules/gulp-angular-gettext": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-angular-gettext/-/gulp-angular-gettext-2.3.0.tgz", + "integrity": "sha512-LzCPhxuL0wvBXN1CQxAN+yH70HFte0x8dSDPW4UZPSOX+SepEeF8Nfgl+1TTOdrrwSk2FOZ/6KKtYUcJee2MvQ==", "dev": true, "dependencies": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" + "angular-gettext-tools": "^2.2.0", + "plugin-error": "^1.0.1", + "through2": "^2.0.1", + "vinyl": "^2.2.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.0" } }, - "node_modules/gulp-eslint/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/gulp-cli": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", "dev": true, + "dependencies": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + }, "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "gulp": "bin/gulp.js" }, "engines": { - "node": ">=4" + "node": ">= 0.10" } }, - "node_modules/gulp-eslint/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/gulp-cli/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, "engines": { - "node": ">=4.0" + "node": ">=0.10.0" } }, - "node_modules/gulp-eslint/node_modules/file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "node_modules/gulp-cli/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", "dev": true, "dependencies": { - "flat-cache": "^2.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/gulp-eslint/node_modules/flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "node_modules/gulp-cli/node_modules/yargs": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", + "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", "dev": true, "dependencies": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - }, - "engines": { - "node": ">=4" + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.1" } }, - "node_modules/gulp-eslint/node_modules/flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "node_modules/gulp-eslint/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/gulp-cli/node_modules/yargs-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", + "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" } }, - "node_modules/gulp-eslint/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "node_modules/gulp-eslint-new": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/gulp-eslint-new/-/gulp-eslint-new-2.4.0.tgz", + "integrity": "sha512-LtuedFIlZS95LcFptwI6SGIkcsh7CsAigpx2z4IbMchGUPHrDUlZde8gNxXNmLR54qBbS+q8AdHC0JWc5DfmOg==", "dev": true, + "license": "MIT", "dependencies": { - "type-fest": "^0.8.1" + "eslint": "8 || 9", + "fancy-log": "^2.0.0", + "plugin-error": "^2.0.1", + "semver": "^7.6.3", + "ternary-stream": "^3.0.0", + "vinyl-fs": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20 || ^14.13 || >=16" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "@types/eslint": "^9.6.1", + "@types/node": ">=12" } }, - "node_modules/gulp-eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/gulp-eslint-new/node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, "engines": { - "node": ">= 4" + "node": ">= 8" } }, - "node_modules/gulp-eslint/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "node_modules/gulp-eslint-new/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-eslint-new/node_modules/fancy-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-2.0.0.tgz", + "integrity": "sha512-9CzxZbACXMUXW13tS0tI8XsGGmxWzO2DmYrGuBJOJ8k8q2K7hwfJA5qHjuPPe8wtsco33YR9wc+Rlr5wYFvhSA==", "dev": true, + "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "color-support": "^1.1.3" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=10.13.0" } }, - "node_modules/gulp-eslint/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "node_modules/gulp-eslint-new/node_modules/fs-mkdirp-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", "dev": true, + "license": "MIT", "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=10.13.0" } }, - "node_modules/gulp-eslint/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/gulp-eslint-new/node_modules/glob-stream": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.2.tgz", + "integrity": "sha512-R8z6eTB55t3QeZMmU1C+Gv+t5UnNRkA55c5yo67fAVfxODxieTwsjNG7utxS/73NdP1NbDgCrhVEg2h00y4fFw==", "dev": true, + "license": "MIT", "dependencies": { - "minimist": "^1.2.6" + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=10.13.0" } }, - "node_modules/gulp-eslint/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "node_modules/gulp-eslint-new/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "license": "MIT", "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=0.10.0" } }, - "node_modules/gulp-eslint/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "node_modules/gulp-eslint-new/node_modules/lead": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", + "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=10.13.0" } }, - "node_modules/gulp-eslint/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "node_modules/gulp-eslint-new/node_modules/now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + }, "engines": { - "node": ">= 0.8.0" + "node": ">= 10.13.0" } }, - "node_modules/gulp-eslint/node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "node_modules/gulp-eslint-new/node_modules/plugin-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-2.0.1.tgz", + "integrity": "sha512-zMakqvIDyY40xHOvzXka0kUvf40nYIuwRE8dWhti2WtjQZ31xAgBZBhxsK7vK3QbRXS1Xms/LO7B5cuAsfB2Gg==", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^1.0.1" + }, "engines": { - "node": ">=0.4.0" + "node": ">=10.13.0" } }, - "node_modules/gulp-eslint/node_modules/regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "node_modules/gulp-eslint-new/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6.5.0" + "node": ">= 10" } }, - "node_modules/gulp-eslint/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/gulp-eslint-new/node_modules/resolve-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", + "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", "dev": true, + "license": "MIT", "dependencies": { - "glob": "^7.1.3" + "value-or-function": "^4.0.0" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">= 10.13.0" } }, - "node_modules/gulp-eslint/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/gulp-eslint-new/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/gulp-eslint/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "node_modules/gulp-eslint-new/node_modules/to-through": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", + "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", "dev": true, + "license": "MIT", "dependencies": { - "shebang-regex": "^1.0.0" + "streamx": "^2.12.5" }, "engines": { - "node": ">=0.10.0" + "node": ">=10.13.0" } }, - "node_modules/gulp-eslint/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "node_modules/gulp-eslint-new/node_modules/value-or-function": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", + "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 10.13.0" } }, - "node_modules/gulp-eslint/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/gulp-eslint/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "node_modules/gulp-eslint-new/node_modules/vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^4.1.0" + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" }, "engines": { - "node": ">=6" + "node": ">=10.13.0" } }, - "node_modules/gulp-eslint/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/gulp-eslint-new/node_modules/vinyl-fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.0.tgz", + "integrity": "sha512-7GbgBnYfaquMk3Qu9g22x000vbYkOex32930rBnc3qByw6HfMEAoELjCjoJv4HuEQxHAurT+nvMHm6MnJllFLw==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.0", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.0", + "vinyl-sourcemap": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=10.13.0" } }, - "node_modules/gulp-eslint/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "node_modules/gulp-eslint-new/node_modules/vinyl-sourcemap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", "dev": true, + "license": "MIT", "dependencies": { - "prelude-ls": "~1.1.2" + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/gulp-eslint/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" + "node": ">=10.13.0" } }, - "node_modules/gulp-eslint/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/gulp-if": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-3.0.0.tgz", + "integrity": "sha512-fCUEngzNiEZEK2YuPm+sdMpO6ukb8+/qzbGfJBXyNOXz85bCG7yBI+pPSl+N90d7gnLvMsarthsAImx0qy7BAw==", "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" + "gulp-match": "^1.1.0", + "ternary-stream": "^3.0.0", + "through2": "^3.0.1" } }, - "node_modules/gulp-if": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-2.0.2.tgz", - "integrity": "sha512-tV0UfXkZodpFq6CYxEqH8tqLQgN6yR9qOhpEEN3O6N5Hfqk3fFLcbAavSex5EqnmoQjyaZ/zvgwclvlTI1KGfw==", + "node_modules/gulp-if/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", "dev": true, + "license": "MIT", "dependencies": { - "gulp-match": "^1.0.3", - "ternary-stream": "^2.0.1", - "through2": "^2.0.1" - }, - "engines": { - "node": ">= 0.10.0" + "inherits": "^2.0.4", + "readable-stream": "2 || 3" } }, "node_modules/gulp-jsonlint": { @@ -9922,6 +11044,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-in-the-middle": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.11.2.tgz", + "integrity": "sha512-gK6Rr6EykBcc6cVWRSBR5TWf8nn6hZMYSRYqCcHa0l0d1fPK7JSYo6+Mlmck76jIX9aL/IZ71c06U2VpFwl1zA==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.8.2", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, "node_modules/imports-loader": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-4.0.1.tgz", @@ -9988,121 +11122,6 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, - "node_modules/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/inquirer/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/inquirer/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -11057,11 +12076,6 @@ "node": ">=8" } }, - "node_modules/jest-worker/node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -13167,13 +14181,10 @@ "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, "node_modules/merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha512-e6RM36aegd4f+r8BZCcYXlO2P3H6xbUM6ktL2Xmf45GAOit9bI4z6/3VU7JwllVO1L7u0UDSg/EhzQ5lmMLolA==", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.1" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", @@ -13438,6 +14449,12 @@ "node": ">=10" } }, + "node_modules/module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==", + "license": "MIT" + }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -13578,12 +14595,6 @@ "node": ">= 0.10" } }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, "node_modules/mv": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", @@ -15270,6 +16281,37 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz", + "integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", @@ -15536,6 +16578,45 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/postman-request": { "version": "2.88.1-postman.34", "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.34.tgz", @@ -16440,6 +17521,13 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true, + "license": "MIT" + }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -16986,6 +18074,43 @@ "node": ">=0.10.0" } }, + "node_modules/require-in-the-middle": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.4.0.tgz", + "integrity": "sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/require-in-the-middle/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/require-in-the-middle/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", @@ -17052,19 +18177,6 @@ "deprecated": "https://github.com/lydell/resolve-url#deprecated", "dev": true }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -17223,15 +18335,6 @@ "npm": ">= 1.4.0" } }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -17255,24 +18358,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/safe-array-concat": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", @@ -17965,6 +19050,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", + "license": "BSD-2-Clause" + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -18039,41 +19130,6 @@ "node": ">=8" } }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -18685,6 +19741,16 @@ "through": "~2.3.4" } }, + "node_modules/stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.13.2" + } + }, "node_modules/stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -18731,6 +19797,21 @@ "node": ">=8.0" } }, + "node_modules/streamx": { + "version": "2.20.2", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.2.tgz", + "integrity": "sha512-aDGDLU+j9tJcUdPGOaHmVF1u/hhI+CsGkT02V3OKlHDV7IukOI+nTWAGkiZEKCO35rWN1wIr4tS7YFr1f4qSvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", @@ -19509,71 +20590,6 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, - "node_modules/table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "dependencies": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/table/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/table/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/table/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/table/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -19603,6 +20619,16 @@ "resolved": "https://registry.npmjs.org/tassembly/-/tassembly-0.2.3.tgz", "integrity": "sha512-rpYD3bbgITu9vxxhRgMDANK3wMUw4WcQ9PJDLJnDJGs13rbHHnfuPB0sVmkTOn0c01BIUe7xZdDcF2IzlgMEHw==" }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, "node_modules/temp": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", @@ -19693,18 +20719,86 @@ } }, "node_modules/ternary-stream": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.1.1.tgz", - "integrity": "sha512-j6ei9hxSoyGlqTmoMjOm+QNvUKDOIY6bNl4Uh1lhBvl6yjPW2iLqxDUYyfDPZknQ4KdRziFl+ec99iT4l7g0cw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-3.0.0.tgz", + "integrity": "sha512-oIzdi+UL/JdktkT+7KU5tSIQjj8pbShj3OASuvDEhm0NT5lppsm7aXWAmAq4/QMaBIyfuEcNLbAQA+HpaISobQ==", "dev": true, + "license": "MIT", "dependencies": { - "duplexify": "^3.5.0", + "duplexify": "^4.1.1", "fork-stream": "^0.0.4", - "merge-stream": "^1.0.0", - "through2": "^2.0.1" + "merge-stream": "^2.0.0", + "through2": "^3.0.1" + } + }, + "node_modules/ternary-stream/node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/ternary-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 6" + } + }, + "node_modules/ternary-stream/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/ternary-stream/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/ternary-stream/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" } }, "node_modules/terser": { @@ -19771,6 +20865,13 @@ "source-map": "^0.6.0" } }, + "node_modules/text-decoder": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.1.tgz", + "integrity": "sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/text-decoding": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", @@ -20361,6 +21462,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -20473,6 +21575,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/ua-parser-js": { "version": "0.7.38", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.38.tgz", @@ -21008,6 +22124,47 @@ "node": ">= 0.10" } }, + "node_modules/vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-contents/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/vinyl-contents/node_modules/vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/vinyl-fs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", @@ -21963,30 +23120,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "node_modules/write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "dependencies": { - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/write/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", @@ -22481,20 +23614,60 @@ "integrity": "sha512-p87QMgXmJS5K8lWkVvYgwmSHmxSsB+N8ctcZZsGx26wOh+0/RiDfsVQAIMv+SME6IWVXIjo/FiZRsGNlvw85Sg==" }, "@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true + }, + "espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "requires": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + } + }, + "globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true + } + } + }, + "@eslint/js": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", + "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "dev": true + }, + "@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", + "dev": true, + "requires": { + "is-negated-glob": "^1.0.0" } }, "@humanwhocodes/config-array": { @@ -22671,6 +23844,653 @@ "fastq": "^1.6.0" } }, + "@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==" + }, + "@opentelemetry/api-logs": { + "version": "0.54.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.54.2.tgz", + "integrity": "sha512-4MTVwwmLgUh5QrJnZpYo6YRO5IBLAggf2h8gWDblwRagDStY13aEvt7gGk3jewrMaPlHiF83fENhIx0HO97/cQ==", + "requires": { + "@opentelemetry/api": "^1.3.0" + } + }, + "@opentelemetry/context-async-hooks": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.28.0.tgz", + "integrity": "sha512-igcl4Ve+F1N2063PJUkesk/GkYyuGIWinYkSyAFTnIj3gzrOgvOA4k747XNdL47HRRL1w/qh7UW8NDuxOLvKFA==", + "requires": {} + }, + "@opentelemetry/core": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.28.0.tgz", + "integrity": "sha512-ZLwRMV+fNDpVmF2WYUdBHlq0eOWtEaUJSusrzjGnBt7iSRvfjFE3RXYUZJrqou/wIDWV0DwQ5KIfYe9WXg9Xqw==", + "requires": { + "@opentelemetry/semantic-conventions": "1.27.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.54.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.54.2.tgz", + "integrity": "sha512-go6zpOVoZVztT9r1aPd79Fr3OWiD4N24bCPJsIKkBses8oyFo12F/Ew3UBTdIu6hsW4HC4MVEJygG6TEyJI/lg==", + "requires": { + "@opentelemetry/api-logs": "0.54.2", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + }, + "@opentelemetry/instrumentation-amqplib": { + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.43.0.tgz", + "integrity": "sha512-ALjfQC+0dnIEcvNYsbZl/VLh7D2P1HhFF4vicRKHhHFIUV3Shpg4kXgiek5PLhmeKSIPiUB25IYH5RIneclL4A==", + "requires": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + } + }, + "@opentelemetry/instrumentation-connect": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.40.0.tgz", + "integrity": "sha512-3aR/3YBQ160siitwwRLjwqrv2KBT16897+bo6yz8wIfel6nWOxTZBJudcbsK3p42pTC7qrbotJ9t/1wRLpv79Q==", + "requires": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.36" + }, + "dependencies": { + "@types/connect": { + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", + "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "requires": { + "@types/node": "*" + } + } + } + }, + "@opentelemetry/instrumentation-dataloader": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.12.0.tgz", + "integrity": "sha512-pnPxatoFE0OXIZDQhL2okF//dmbiWFzcSc8pUg9TqofCLYZySSxDCgQc69CJBo5JnI3Gz1KP+mOjS4WAeRIH4g==", + "requires": { + "@opentelemetry/instrumentation": "^0.53.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/instrumentation-express": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.44.0.tgz", + "integrity": "sha512-GWgibp6Q0wxyFaaU8ERIgMMYgzcHmGrw3ILUtGchLtLncHNOKk0SNoWGqiylXWWT4HTn5XdV8MGawUgpZh80cA==", + "requires": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + } + }, + "@opentelemetry/instrumentation-fastify": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.41.0.tgz", + "integrity": "sha512-pNRjFvf0mvqfJueaeL/qEkuGJwgtE5pgjIHGYwjc2rMViNCrtY9/Sf+Nu8ww6dDd/Oyk2fwZZP7i0XZfCnETrA==", + "requires": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + } + }, + "@opentelemetry/instrumentation-fs": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.16.0.tgz", + "integrity": "sha512-hMDRUxV38ln1R3lNz6osj3YjlO32ykbHqVrzG7gEhGXFQfu7LJUx8t9tEwE4r2h3CD4D0Rw4YGDU4yF4mP3ilg==", + "requires": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.54.0" + } + }, + "@opentelemetry/instrumentation-generic-pool": { + "version": "0.39.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.39.0.tgz", + "integrity": "sha512-y4v8Y+tSfRB3NNBvHjbjrn7rX/7sdARG7FuK6zR8PGb28CTa0kHpEGCJqvL9L8xkTNvTXo+lM36ajFGUaK1aNw==", + "requires": { + "@opentelemetry/instrumentation": "^0.53.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/instrumentation-graphql": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.44.0.tgz", + "integrity": "sha512-FYXTe3Bv96aNpYktqm86BFUTpjglKD0kWI5T5bxYkLUPEPvFn38vWGMJTGrDMVou/i55E4jlWvcm6hFIqLsMbg==", + "requires": { + "@opentelemetry/instrumentation": "^0.54.0" + } + }, + "@opentelemetry/instrumentation-hapi": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.41.0.tgz", + "integrity": "sha512-jKDrxPNXDByPlYcMdZjNPYCvw0SQJjN+B1A+QH+sx+sAHsKSAf9hwFiJSrI6C4XdOls43V/f/fkp9ITkHhKFbQ==", + "requires": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/instrumentation-http": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.53.0.tgz", + "integrity": "sha512-H74ErMeDuZfj7KgYCTOFGWF5W9AfaPnqLQQxeFq85+D29wwV2yqHbz2IKLYpkOh7EI6QwDEl7rZCIxjJLyc/CQ==", + "requires": { + "@opentelemetry/core": "1.26.0", + "@opentelemetry/instrumentation": "0.53.0", + "@opentelemetry/semantic-conventions": "1.27.0", + "semver": "^7.5.2" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/core": { + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.26.0.tgz", + "integrity": "sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ==", + "requires": { + "@opentelemetry/semantic-conventions": "1.27.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/instrumentation-ioredis": { + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.43.0.tgz", + "integrity": "sha512-i3Dke/LdhZbiUAEImmRG3i7Dimm/BD7t8pDDzwepSvIQ6s2X6FPia7561gw+64w+nx0+G9X14D7rEfaMEmmjig==", + "requires": { + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/instrumentation-kafkajs": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.4.0.tgz", + "integrity": "sha512-I9VwDG314g7SDL4t8kD/7+1ytaDBRbZQjhVaQaVIDR8K+mlsoBhLsWH79yHxhHQKvwCSZwqXF+TiTOhoQVUt7A==", + "requires": { + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + } + }, + "@opentelemetry/instrumentation-knex": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.41.0.tgz", + "integrity": "sha512-OhI1SlLv5qnsnm2dOVrian/x3431P75GngSpnR7c4fcVFv7prXGYu29Z6ILRWJf/NJt6fkbySmwdfUUnFnHCTg==", + "requires": { + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + } + }, + "@opentelemetry/instrumentation-koa": { + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.43.0.tgz", + "integrity": "sha512-lDAhSnmoTIN6ELKmLJBplXzT/Jqs5jGZehuG22EdSMaTwgjMpxMDI1YtlKEhiWPWkrz5LUsd0aOO0ZRc9vn3AQ==", + "requires": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.40.0.tgz", + "integrity": "sha512-21xRwZsEdMPnROu/QsaOIODmzw59IYpGFmuC4aFWvMj6stA8+Ei1tX67nkarJttlNjoM94um0N4X26AD7ff54A==", + "requires": { + "@opentelemetry/instrumentation": "^0.53.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/instrumentation-mongodb": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.48.0.tgz", + "integrity": "sha512-9YWvaGvrrcrydMsYGLu0w+RgmosLMKe3kv/UNlsPy8RLnCkN2z+bhhbjjjuxtUmvEuKZMCoXFluABVuBr1yhjw==", + "requires": { + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + } + }, + "@opentelemetry/instrumentation-mongoose": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.42.0.tgz", + "integrity": "sha512-AnWv+RaR86uG3qNEMwt3plKX1ueRM7AspfszJYVkvkehiicC3bHQA6vWdb6Zvy5HAE14RyFbu9+2hUUjR2NSyg==", + "requires": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/instrumentation-mysql": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.41.0.tgz", + "integrity": "sha512-jnvrV6BsQWyHS2qb2fkfbfSb1R/lmYwqEZITwufuRl37apTopswu9izc0b1CYRp/34tUG/4k/V39PND6eyiNvw==", + "requires": { + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/mysql": "2.15.26" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/instrumentation-mysql2": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.41.0.tgz", + "integrity": "sha512-REQB0x+IzVTpoNgVmy5b+UnH1/mDByrneimP6sbDHkp1j8QOl1HyWOrBH/6YWR0nrbU3l825Em5PlybjT3232g==", + "requires": { + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@opentelemetry/sql-common": "^0.40.1" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/instrumentation-nestjs-core": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.40.0.tgz", + "integrity": "sha512-WF1hCUed07vKmf5BzEkL0wSPinqJgH7kGzOjjMAiTGacofNXjb/y4KQ8loj2sNsh5C/NN7s1zxQuCgbWbVTGKg==", + "requires": { + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/instrumentation-pg": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.44.0.tgz", + "integrity": "sha512-oTWVyzKqXud1BYEGX1loo2o4k4vaU1elr3vPO8NZolrBtFvQ34nx4HgUaexUDuEog00qQt+MLR5gws/p+JXMLQ==", + "requires": { + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@opentelemetry/sql-common": "^0.40.1", + "@types/pg": "8.6.1", + "@types/pg-pool": "2.0.6" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/instrumentation-redis-4": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.42.0.tgz", + "integrity": "sha512-NaD+t2JNcOzX/Qa7kMy68JbmoVIV37fT/fJYzLKu2Wwd+0NCxt+K2OOsOakA8GVg8lSpFdbx4V/suzZZ2Pvdjg==", + "requires": { + "@opentelemetry/instrumentation": "^0.53.0", + "@opentelemetry/redis-common": "^0.36.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/instrumentation-tedious": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.15.0.tgz", + "integrity": "sha512-Kb7yo8Zsq2TUwBbmwYgTAMPK0VbhoS8ikJ6Bup9KrDtCx2JC01nCb+M0VJWXt7tl0+5jARUbKWh5jRSoImxdCw==", + "requires": { + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/tedious": "^4.0.14" + } + }, + "@opentelemetry/instrumentation-undici": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.6.0.tgz", + "integrity": "sha512-ABJBhm5OdhGmbh0S/fOTE4N69IZ00CsHC5ijMYfzbw3E5NwLgpQk5xsljaECrJ8wz1SfXbO03FiSuu5AyRAkvQ==", + "requires": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.53.0" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz", + "integrity": "sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.53.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz", + "integrity": "sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==", + "requires": { + "@opentelemetry/api-logs": "0.53.0", + "@types/shimmer": "^1.2.0", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, + "@opentelemetry/redis-common": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz", + "integrity": "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==" + }, + "@opentelemetry/resources": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.28.0.tgz", + "integrity": "sha512-cIyXSVJjGeTICENN40YSvLDAq4Y2502hGK3iN7tfdynQLKWb3XWZQEkPc+eSx47kiy11YeFAlYkEfXwR1w8kfw==", + "requires": { + "@opentelemetry/core": "1.28.0", + "@opentelemetry/semantic-conventions": "1.27.0" + } + }, + "@opentelemetry/sdk-trace-base": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.28.0.tgz", + "integrity": "sha512-ceUVWuCpIao7Y5xE02Xs3nQi0tOGmMea17ecBdwtCvdo9ekmO+ijc9RFDgfifMl7XCBf41zne/1POM3LqSTZDA==", + "requires": { + "@opentelemetry/core": "1.28.0", + "@opentelemetry/resources": "1.28.0", + "@opentelemetry/semantic-conventions": "1.27.0" + } + }, + "@opentelemetry/semantic-conventions": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz", + "integrity": "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==" + }, + "@opentelemetry/sql-common": { + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz", + "integrity": "sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==", + "requires": { + "@opentelemetry/core": "^1.1.0" + } + }, "@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -22718,11 +24538,120 @@ "safe-buffer": "^5.0.1" } }, + "@prisma/instrumentation": { + "version": "5.19.1", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-5.19.1.tgz", + "integrity": "sha512-VLnzMQq7CWroL5AeaW0Py2huiNKeoMfCH3SUxstdzPrlWQi6UQ9UrfcbUkNHlVFqOMacqy8X/8YtE0kuKDpD9w==", + "requires": { + "@opentelemetry/api": "^1.8", + "@opentelemetry/instrumentation": "^0.49 || ^0.50 || ^0.51 || ^0.52.0", + "@opentelemetry/sdk-trace-base": "^1.22" + }, + "dependencies": { + "@opentelemetry/api-logs": { + "version": "0.52.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz", + "integrity": "sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A==", + "requires": { + "@opentelemetry/api": "^1.0.0" + } + }, + "@opentelemetry/instrumentation": { + "version": "0.52.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.52.1.tgz", + "integrity": "sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==", + "requires": { + "@opentelemetry/api-logs": "0.52.1", + "@types/shimmer": "^1.0.2", + "import-in-the-middle": "^1.8.1", + "require-in-the-middle": "^7.1.1", + "semver": "^7.5.2", + "shimmer": "^1.2.1" + } + } + } + }, "@sailshq/lodash": { "version": "3.10.6", "resolved": "https://registry.npmjs.org/@sailshq/lodash/-/lodash-3.10.6.tgz", "integrity": "sha512-gp2pRE/kyh3DbrQ7MaI65xNcjrToZHRa52XJsNB8kZ0Aj0fcDNQPw8entkcuaaPYzCPxXau4rpOXaw/rl0c/ZQ==" }, + "@sentry/core": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.38.0.tgz", + "integrity": "sha512-sGD+5TEHU9G7X7zpyaoJxpOtwjTjvOd1f/MKBrWW2vf9UbYK+GUJrOzLhMoSWp/pHSYgvObkJkDb/HwieQjvhQ==", + "requires": { + "@sentry/types": "8.38.0", + "@sentry/utils": "8.38.0" + } + }, + "@sentry/node": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-8.38.0.tgz", + "integrity": "sha512-nwW0XqZFQseXYn0i6i6nKPkbjgHMBEFSF9TnK6mHHqJHHObHIZ6qu5CfvGKgxATia8JPIg9NN8XcyYOnQMi07w==", + "requires": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.25.1", + "@opentelemetry/core": "^1.25.1", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/instrumentation-amqplib": "^0.43.0", + "@opentelemetry/instrumentation-connect": "0.40.0", + "@opentelemetry/instrumentation-dataloader": "0.12.0", + "@opentelemetry/instrumentation-express": "0.44.0", + "@opentelemetry/instrumentation-fastify": "0.41.0", + "@opentelemetry/instrumentation-fs": "0.16.0", + "@opentelemetry/instrumentation-generic-pool": "0.39.0", + "@opentelemetry/instrumentation-graphql": "0.44.0", + "@opentelemetry/instrumentation-hapi": "0.41.0", + "@opentelemetry/instrumentation-http": "0.53.0", + "@opentelemetry/instrumentation-ioredis": "0.43.0", + "@opentelemetry/instrumentation-kafkajs": "0.4.0", + "@opentelemetry/instrumentation-knex": "0.41.0", + "@opentelemetry/instrumentation-koa": "0.43.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.40.0", + "@opentelemetry/instrumentation-mongodb": "0.48.0", + "@opentelemetry/instrumentation-mongoose": "0.42.0", + "@opentelemetry/instrumentation-mysql": "0.41.0", + "@opentelemetry/instrumentation-mysql2": "0.41.0", + "@opentelemetry/instrumentation-nestjs-core": "0.40.0", + "@opentelemetry/instrumentation-pg": "0.44.0", + "@opentelemetry/instrumentation-redis-4": "0.42.0", + "@opentelemetry/instrumentation-tedious": "0.15.0", + "@opentelemetry/instrumentation-undici": "0.6.0", + "@opentelemetry/resources": "^1.26.0", + "@opentelemetry/sdk-trace-base": "^1.26.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@prisma/instrumentation": "5.19.1", + "@sentry/core": "8.38.0", + "@sentry/opentelemetry": "8.38.0", + "@sentry/types": "8.38.0", + "@sentry/utils": "8.38.0", + "import-in-the-middle": "^1.11.2" + } + }, + "@sentry/opentelemetry": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-8.38.0.tgz", + "integrity": "sha512-AfjmIf/v7+x2WplhkX66LyGKvrzzPeSgff9uJ0cFCC2s0yd1qA2VPuIwEyr5i/FOJOP5bvFr8tu/hz3LA4+F5Q==", + "requires": { + "@sentry/core": "8.38.0", + "@sentry/types": "8.38.0", + "@sentry/utils": "8.38.0" + } + }, + "@sentry/types": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.38.0.tgz", + "integrity": "sha512-fP5H9ZX01W4Z/EYctk3mkSHi7d06cLcX2/UWqwdWbyPWI+pL2QpUPICeO/C+8SnmYx//wFj3qWDhyPCh1PdFAA==" + }, + "@sentry/utils": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.38.0.tgz", + "integrity": "sha512-3X7MgIKIx+2q5Al7QkhaRB4wV6DvzYsaeIwdqKUzGLuRjXmNgJrLoU87TAwQRmZ6Wr3IoEpThZZMNrzYPXxArw==", + "requires": { + "@sentry/types": "8.38.0" + } + }, "@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -22793,9 +24722,9 @@ } }, "@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -22866,6 +24795,14 @@ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, + "@types/mysql": { + "version": "2.15.26", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.26.tgz", + "integrity": "sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==", + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "20.14.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.6.tgz", @@ -22888,6 +24825,24 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, + "@types/pg": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", + "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", + "requires": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "@types/pg-pool": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", + "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", + "requires": { + "@types/pg": "*" + } + }, "@types/pug": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", @@ -22950,6 +24905,11 @@ "@types/send": "*" } }, + "@types/shimmer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==" + }, "@types/sockjs": { "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", @@ -22959,6 +24919,14 @@ "@types/node": "*" } }, + "@types/tedious": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", + "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", + "requires": { + "@types/node": "*" + } + }, "@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -23145,9 +25113,9 @@ } }, "acorn": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", - "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==" + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==" }, "acorn-globals": { "version": "3.1.0", @@ -23388,23 +25356,6 @@ "ansi-wrap": "^0.1.0" } }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, "ansi-gray": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", @@ -23784,12 +25735,6 @@ "integrity": "sha512-qEdtR2UH78yyHX/AUNfXmJTlM48XoFZKBdwi1nzkI1mJL21cmbu0cvjxjpkXJ5NENMq42H+hNs8VLJcqXLerBQ==", "dev": true }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, "async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -24016,6 +25961,13 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "bare-events": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", + "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", + "dev": true, + "optional": true + }, "barrage": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/barrage/-/barrage-1.1.0.tgz", @@ -24136,6 +26088,61 @@ "file-uri-to-path": "1.0.0" } }, + "bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "requires": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, "blocking-proxy": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", @@ -24554,12 +26561,6 @@ "is-regex": "^1.0.3" } }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, "cheerio": { "version": "0.22.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", @@ -24652,6 +26653,11 @@ "safe-buffer": "^5.0.1" } }, + "cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==" + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -24694,15 +26700,6 @@ "source-map": "~0.6.0" } }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, "cli-docs-generator": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/cli-docs-generator/-/cli-docs-generator-1.0.7.tgz", @@ -24885,12 +26882,6 @@ } } }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true - }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -25343,9 +27334,9 @@ "dev": true }, "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.1.tgz", + "integrity": "sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==" }, "cookie-parser": { "version": "1.4.6", @@ -25354,6 +27345,13 @@ "requires": { "cookie": "0.4.1", "cookie-signature": "1.0.6" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + } } }, "cookie-session": { @@ -26336,6 +28334,11 @@ "ws": "~8.17.1" }, "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, "engine.io-parser": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", @@ -26650,6 +28653,23 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { + "@eslint/eslintrc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -26702,6 +28722,15 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, "globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -26921,12 +28950,6 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true } } }, @@ -27150,28 +29173,6 @@ "is-extendable": "^0.1.0" } }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "dependencies": { - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - } - } - }, "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -27226,6 +29227,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -27346,15 +29353,6 @@ "pend": "~1.2.0" } }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -27948,13 +29946,10 @@ } }, "globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.12.0.tgz", + "integrity": "sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==", + "dev": true }, "globalthis": { "version": "1.0.4", @@ -28518,387 +30513,214 @@ } } }, - "gulp-eslint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-6.0.0.tgz", - "integrity": "sha512-dCVPSh1sA+UVhn7JSQt7KEb4An2sQNbOdB3PA8UCfxsoPlAKjJHxYHGXdXC7eb+V1FAnilSFFqslPrq037l1ig==", + "gulp-eslint-new": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/gulp-eslint-new/-/gulp-eslint-new-2.4.0.tgz", + "integrity": "sha512-LtuedFIlZS95LcFptwI6SGIkcsh7CsAigpx2z4IbMchGUPHrDUlZde8gNxXNmLR54qBbS+q8AdHC0JWc5DfmOg==", "dev": true, "requires": { - "eslint": "^6.0.0", - "fancy-log": "^1.3.2", - "plugin-error": "^1.0.1" + "@types/eslint": "^9.6.1", + "@types/node": ">=12", + "eslint": "8 || 9", + "fancy-log": "^2.0.0", + "plugin-error": "^2.0.1", + "semver": "^7.6.3", + "ternary-stream": "^3.0.0", + "vinyl-fs": "^4.0.0" }, "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - } - } - }, - "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "4.17.21", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.3", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - }, - "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "requires": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" } }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "fancy-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-2.0.0.tgz", + "integrity": "sha512-9CzxZbACXMUXW13tS0tI8XsGGmxWzO2DmYrGuBJOJ8k8q2K7hwfJA5qHjuPPe8wtsco33YR9wc+Rlr5wYFvhSA==", "dev": true, "requires": { - "flat-cache": "^2.0.1" + "color-support": "^1.1.3" } }, - "flat-cache": { + "fs-mkdirp-stream": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", "dev": true, "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" } }, - "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "glob-stream": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.2.tgz", + "integrity": "sha512-R8z6eTB55t3QeZMmU1C+Gv+t5UnNRkA55c5yo67fAVfxODxieTwsjNG7utxS/73NdP1NbDgCrhVEg2h00y4fFw==", "dev": true, "requires": { - "is-glob": "^4.0.1" + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" } }, - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" } }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "lead": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", + "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", "dev": true }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", "dev": true, "requires": { - "minimist": "^1.2.6" + "once": "^1.4.0" } }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "plugin-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-2.0.1.tgz", + "integrity": "sha512-zMakqvIDyY40xHOvzXka0kUvf40nYIuwRE8dWhti2WtjQZ31xAgBZBhxsK7vK3QbRXS1Xms/LO7B5cuAsfB2Gg==", "dev": true, "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "ansi-colors": "^1.0.1" } }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", "dev": true }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "resolve-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", + "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", "dev": true, "requires": { - "glob": "^7.1.3" + "value-or-function": "^4.0.0" } }, "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "to-through": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", + "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "streamx": "^2.12.5" } }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "value-or-function": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", + "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", "dev": true }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" } }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "vinyl-fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.0.tgz", + "integrity": "sha512-7GbgBnYfaquMk3Qu9g22x000vbYkOex32930rBnc3qByw6HfMEAoELjCjoJv4HuEQxHAurT+nvMHm6MnJllFLw==", + "dev": true, + "requires": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.0", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.0", + "vinyl-sourcemap": "^2.0.0" + } + }, + "vinyl-sourcemap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", "dev": true, "requires": { - "isexe": "^2.0.0" + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" } } } }, - "gulp-eslint-if-fixed": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulp-eslint-if-fixed/-/gulp-eslint-if-fixed-1.0.0.tgz", - "integrity": "sha512-Cx8SNxafW1sfkERGpprl6vduMsT/LEqhhhcnA6ieUzDUU8u6Tq8963GcqWhWPVYYxrME4rwalsYU7DmbkdFhLw==", - "dev": true, - "requires": { - "gulp-if": "^2.0.0" - } - }, "gulp-if": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-2.0.2.tgz", - "integrity": "sha512-tV0UfXkZodpFq6CYxEqH8tqLQgN6yR9qOhpEEN3O6N5Hfqk3fFLcbAavSex5EqnmoQjyaZ/zvgwclvlTI1KGfw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-3.0.0.tgz", + "integrity": "sha512-fCUEngzNiEZEK2YuPm+sdMpO6ukb8+/qzbGfJBXyNOXz85bCG7yBI+pPSl+N90d7gnLvMsarthsAImx0qy7BAw==", "dev": true, "requires": { - "gulp-match": "^1.0.3", - "ternary-stream": "^2.0.1", - "through2": "^2.0.1" + "gulp-match": "^1.1.0", + "ternary-stream": "^3.0.0", + "through2": "^3.0.1" + }, + "dependencies": { + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } } }, "gulp-jsonlint": { @@ -30074,6 +31896,17 @@ "resolve-from": "^4.0.0" } }, + "import-in-the-middle": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.11.2.tgz", + "integrity": "sha512-gK6Rr6EykBcc6cVWRSBR5TWf8nn6hZMYSRYqCcHa0l0d1fPK7JSYo6+Mlmck76jIX9aL/IZ71c06U2VpFwl1zA==", + "requires": { + "acorn": "^8.8.2", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, "imports-loader": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-4.0.1.tgz", @@ -30120,93 +31953,6 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, - "inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "4.17.21", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -30904,11 +32650,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -32641,13 +34382,9 @@ "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha512-e6RM36aegd4f+r8BZCcYXlO2P3H6xbUM6ktL2Xmf45GAOit9bI4z6/3VU7JwllVO1L7u0UDSg/EhzQ5lmMLolA==", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "merge2": { "version": "1.4.1", @@ -32848,6 +34585,11 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, + "module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" + }, "moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -32933,12 +34675,6 @@ "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", "dev": true }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, "mv": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", @@ -34284,6 +36020,28 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-protocol": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz", + "integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, "picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", @@ -34466,6 +36224,29 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, "postman-request": { "version": "2.88.1-postman.34", "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.34.tgz", @@ -35201,6 +36982,12 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -35634,6 +37421,31 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" }, + "require-in-the-middle": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.4.0.tgz", + "integrity": "sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==", + "requires": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + }, + "dependencies": { + "debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", @@ -35684,16 +37496,6 @@ "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", "dev": true }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -35807,12 +37609,6 @@ "@sailshq/lodash": "^3.10.2" } }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true - }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -35822,23 +37618,6 @@ "queue-microtask": "^1.2.2" } }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "safe-array-concat": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", @@ -36372,6 +38151,11 @@ "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", "dev": true }, + "shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, "side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -36430,34 +38214,6 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true - } - } - }, "smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -36950,6 +38706,15 @@ "through": "~2.3.4" } }, + "stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", + "dev": true, + "requires": { + "streamx": "^2.13.2" + } + }, "stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -36993,6 +38758,18 @@ "fs-extra": "^8.1.0" } }, + "streamx": { + "version": "2.20.2", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.2.tgz", + "integrity": "sha512-aDGDLU+j9tJcUdPGOaHmVF1u/hhI+CsGkT02V3OKlHDV7IukOI+nTWAGkiZEKCO35rWN1wIr4tS7YFr1f4qSvA==", + "dev": true, + "requires": { + "bare-events": "^2.2.0", + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + } + }, "strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", @@ -37620,58 +39397,6 @@ } } }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "lodash": "4.17.21", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -37695,6 +39420,15 @@ "resolved": "https://registry.npmjs.org/tassembly/-/tassembly-0.2.3.tgz", "integrity": "sha512-rpYD3bbgITu9vxxhRgMDANK3wMUw4WcQ9PJDLJnDJGs13rbHHnfuPB0sVmkTOn0c01BIUe7xZdDcF2IzlgMEHw==" }, + "teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "requires": { + "streamx": "^2.12.5" + } + }, "temp": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", @@ -37770,15 +39504,65 @@ } }, "ternary-stream": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.1.1.tgz", - "integrity": "sha512-j6ei9hxSoyGlqTmoMjOm+QNvUKDOIY6bNl4Uh1lhBvl6yjPW2iLqxDUYyfDPZknQ4KdRziFl+ec99iT4l7g0cw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-3.0.0.tgz", + "integrity": "sha512-oIzdi+UL/JdktkT+7KU5tSIQjj8pbShj3OASuvDEhm0NT5lppsm7aXWAmAq4/QMaBIyfuEcNLbAQA+HpaISobQ==", "dev": true, "requires": { - "duplexify": "^3.5.0", + "duplexify": "^4.1.1", "fork-stream": "^0.0.4", - "merge-stream": "^1.0.0", - "through2": "^2.0.1" + "merge-stream": "^2.0.0", + "through2": "^3.0.1" + }, + "dependencies": { + "duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "dev": true, + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } } }, "terser": { @@ -37820,6 +39604,12 @@ "terser": "^5.26.0" } }, + "text-decoder": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.1.tgz", + "integrity": "sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==", + "dev": true + }, "text-decoding": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", @@ -38372,6 +40162,12 @@ "typed-array-byte-offset": "^1.0.2" } }, + "typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true + }, "ua-parser-js": { "version": "0.7.38", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.38.tgz", @@ -38794,6 +40590,37 @@ "replace-ext": "^1.0.0" } }, + "vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", + "dev": true, + "requires": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, + "dependencies": { + "replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true + }, + "vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + } + } + } + }, "vinyl-fs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", @@ -39487,26 +41314,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "requires": { - "minimist": "^1.2.6" - } - } - } - }, "ws": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", diff --git a/package.json b/package.json index 421df84723..e8c6432063 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vk-devicehub", - "version": "1.3.4", + "version": "1.3.5", "description": "Smartphone Test Farm", "type": "module", "keywords": [ @@ -12,7 +12,7 @@ "remote" ], "bin": { - "stf": "./bin/stf" + "stf": "./bin/stf.mjs" }, "yargs": { "duplicate-arguments-array": false @@ -37,6 +37,7 @@ "@devicefarmer/stf-syrup": "1.0.2", "@devicefarmer/stf-wiki": "1.0.0", "@julusian/jpeg-turbo": "2.1.0", + "@sentry/node": "^8.34.0", "android-device-list": "1.2.10", "angular": "1.8.3", "angular-animate": "1.8.3", @@ -60,8 +61,10 @@ "chalk": "1.1.3", "components-font-awesome": "4.7.0", "compression": "1.7.4", + "cookie": "^1.0.1", "cookie-parser": "^1.4.6", "cookie-session": "2.0.0", + "cors": "^2.8.5", "csurf": "1.11.0", "d3": "3.5.17", "debug": "4.3.4", @@ -111,10 +114,10 @@ "proxy-addr": "2.0.7", "pug": "3.0.2", "query-string": "7.1.1", - "rethinkdb": "^2.4.2", + "request": "npm:postman-request@2.88.1-postman.33", "request-progress": "2.0.1", "request-promise": "4.2.6", - "request": "npm:postman-request@2.88.1-postman.33", + "rethinkdb": "^2.4.2", "rfb2": "^0.2.2", "rimraf": "5.0.7", "semver": "7.5.3", @@ -132,8 +135,8 @@ "url-join": "1.1.0", "utf-8-validate": "5.0.9", "uuid": "3.4.0", - "websocket-stream": "5.5.2", "webpack": "^5.88.1", + "websocket-stream": "5.5.2", "ws": "3.3.3", "yaml": "2.4.2", "yargs": "^17.7.2", @@ -141,6 +144,8 @@ "zeromq": "^5.0.0" }, "devDependencies": { + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "^9.13.0", "angular-mocks": "1.8.3", "async": "2.6.4", "chai": "3.5.0", @@ -151,10 +156,11 @@ "event-stream": "3.3.5", "exports-loader": "^4.0.0", "fs-extra": "8.1.0", + "globals": "^15.11.0", "gulp": "4.0.2", "gulp-angular-gettext": "2.3.0", - "gulp-eslint": "6.0.0", - "gulp-eslint-if-fixed": "^1.0.0", + "gulp-eslint-new": "^2.3.0", + "gulp-if": "^3.0.0", "gulp-jsonlint": "1.3.2", "gulp-protractor": "3.0.0", "gulp-pug": "4.0.1", @@ -188,6 +194,7 @@ "style-loader": "^3.3.3", "template-html-loader": "0.0.4", "then-jade": "2.4.4", + "typescript": "^5.5.3", "webpack-dev-server": "^4.15.1" }, "overrides": { diff --git a/res/app/app.js b/res/app/app.js index db22373fdb..277dc6e2d6 100644 --- a/res/app/app.js +++ b/res/app/app.js @@ -3,38 +3,38 @@ **/ require.ensure([], function(require) { - require('angular') - require('angular-route') - require('angular-touch') + require('angular') + require('angular-route') + require('angular-touch') - angular.module('app', [ - 'ngRoute', - 'ngTouch', - require('gettext').name, - require('angular-hotkeys').name, - require('./layout').name, - require('./device-list').name, - require('./group-list').name, - require('./control-panes').name, - require('./menu').name, - require('./settings').name, - require('./docs').name, - require('./user').name, - require('./../common/lang').name, - require('stf/standalone').name, - require('./group-list').name - ]) - .config(function($routeProvider, $locationProvider) { - $locationProvider.hashPrefix('!') - $routeProvider - .otherwise({ - redirectTo: '/devices' + angular.module('app', [ + 'ngRoute', + 'ngTouch', + require('gettext').name, + require('angular-hotkeys').name, + require('./layout').name, + require('./device-list').name, + require('./group-list').name, + require('./control-panes').name, + require('./menu').name, + require('./settings').name, + require('./docs').name, + require('./user').name, + require('./../common/lang').name, + require('stf/standalone').name, + require('./group-list').name + ]) + .config(function($routeProvider, $locationProvider) { + $locationProvider.hashPrefix('!') + $routeProvider + .otherwise({ + redirectTo: '/devices' + }) }) - }) - .config(function(hotkeysProvider) { - hotkeysProvider.templateTitle = 'Keyboard Shortcuts:' - }) + .config(function(hotkeysProvider) { + hotkeysProvider.templateTitle = 'Keyboard Shortcuts:' + }) }) const nodeInfo = require('../../package.json') diff --git a/res/app/components/stf/admin-mode/admin-mode-directive.js b/res/app/components/stf/admin-mode/admin-mode-directive.js index 3c47abf0b9..d09590b18e 100644 --- a/res/app/components/stf/admin-mode/admin-mode-directive.js +++ b/res/app/components/stf/admin-mode/admin-mode-directive.js @@ -1,11 +1,11 @@ module.exports = function adminModeDirective($rootScope, SettingsService) { - return { - restrict: 'AE', - link: function() { - SettingsService.bind($rootScope, { - target: 'adminMode', - defaultValue: false - }) + return { + restrict: 'AE' + , link: function() { + SettingsService.bind($rootScope, { + target: 'adminMode' + , defaultValue: false + }) + } } - } } diff --git a/res/app/components/stf/admin-mode/admin-mode-spec.js b/res/app/components/stf/admin-mode/admin-mode-spec.js index 0bee139b3e..2174be2223 100644 --- a/res/app/components/stf/admin-mode/admin-mode-spec.js +++ b/res/app/components/stf/admin-mode/admin-mode-spec.js @@ -1,3 +1,3 @@ describe('AdminModeService', function() { - beforeEach(angular.mock.module(require('./').name)) + beforeEach(angular.mock.module(require('./').name)) }) diff --git a/res/app/components/stf/admin-mode/index.js b/res/app/components/stf/admin-mode/index.js index 6f1a9cd1fd..85c6b4c93a 100644 --- a/res/app/components/stf/admin-mode/index.js +++ b/res/app/components/stf/admin-mode/index.js @@ -1,4 +1,4 @@ module.exports = angular.module('stf.admin-mode', [ ]) - .directive('adminMode', require('./admin-mode-directive')) + .directive('adminMode', require('./admin-mode-directive')) diff --git a/res/app/components/stf/angular-draggabilly/angular-draggabilly-directive.js b/res/app/components/stf/angular-draggabilly/angular-draggabilly-directive.js index 20536941af..df3ca37d79 100644 --- a/res/app/components/stf/angular-draggabilly/angular-draggabilly-directive.js +++ b/res/app/components/stf/angular-draggabilly/angular-draggabilly-directive.js @@ -1,18 +1,18 @@ module.exports = function angularDraggabillyDirective(DraggabillyService, $parse) { - return { - restrict: 'AE', - link: function(scope, element, attrs) { - var parsedAttrs = $parse(attrs.angularDraggabilly)() - if (typeof parsedAttrs !== 'object') { - parsedAttrs = {} - } + return { + restrict: 'AE' + , link: function(scope, element, attrs) { + var parsedAttrs = $parse(attrs.angularDraggabilly)() + if (typeof parsedAttrs !== 'object') { + parsedAttrs = {} + } - var options = angular.extend({ - }, parsedAttrs) + var options = angular.extend({ + }, parsedAttrs) - /* eslint no-unused-vars: 0 */ - var draggie = new DraggabillyService(element[0], options) + /* eslint no-unused-vars: 0 */ + var draggie = new DraggabillyService(element[0], options) + } } - } } diff --git a/res/app/components/stf/angular-draggabilly/angular-draggabilly-spec.js b/res/app/components/stf/angular-draggabilly/angular-draggabilly-spec.js index 3c32e5a720..cf7229fa04 100644 --- a/res/app/components/stf/angular-draggabilly/angular-draggabilly-spec.js +++ b/res/app/components/stf/angular-draggabilly/angular-draggabilly-spec.js @@ -1,15 +1,15 @@ describe('angularDraggabilly', function() { + beforeEach(angular.mock.module(require('./').name)) - beforeEach(angular.mock.module(require('./').name)) + var scope, compile - var scope, compile + beforeEach(inject(function($rootScope, $compile) { + scope = $rootScope.$new() + compile = $compile + })) - beforeEach(inject(function($rootScope, $compile) { - scope = $rootScope.$new() - compile = $compile - })) + it('should ...', function() { - it('should ...', function() { /* To test your directive, you need to create some html that would use your directive, send that through compile() then compare the results. @@ -17,5 +17,5 @@ describe('angularDraggabilly', function() { var element = compile('
hi
')(scope) expect(element.text()).toBe('hello, world') */ - }) + }) }) diff --git a/res/app/components/stf/angular-draggabilly/index.js b/res/app/components/stf/angular-draggabilly/index.js index 3d64ef5298..a0679d7113 100644 --- a/res/app/components/stf/angular-draggabilly/index.js +++ b/res/app/components/stf/angular-draggabilly/index.js @@ -3,7 +3,7 @@ var draggabilly = require('draggabilly') module.exports = angular.module('stf.angular-draggabilly', [ ]) - .factory('DraggabillyService', function() { - return draggabilly - }) - .directive('angularDraggabilly', require('./angular-draggabilly-directive')) + .factory('DraggabillyService', function() { + return draggabilly + }) + .directive('angularDraggabilly', require('./angular-draggabilly-directive')) diff --git a/res/app/components/stf/angular-packery/angular-packery-directive.js b/res/app/components/stf/angular-packery/angular-packery-directive.js index 10b240d21d..aa75a23188 100644 --- a/res/app/components/stf/angular-packery/angular-packery-directive.js +++ b/res/app/components/stf/angular-packery/angular-packery-directive.js @@ -1,66 +1,65 @@ var _ = require('lodash') module.exports = function angularPackeryDirective(PackeryService, - DraggabillyService, $timeout, $parse) { + DraggabillyService, $timeout, $parse) { + return { + restrict: 'AE' + , link: function(scope, element, attrs) { + var container = element[0] + var parsedAttrs = $parse(attrs.angularPackery)() + if (typeof parsedAttrs !== 'object') { + parsedAttrs = {} + } - return { - restrict: 'AE', - link: function(scope, element, attrs) { - var container = element[0] - var parsedAttrs = $parse(attrs.angularPackery)() - if (typeof parsedAttrs !== 'object') { - parsedAttrs = {} - } + var options = angular.extend({ + isInitLayout: false + , itemSelector: '.packery-item' + , columnWidth: '.packery-item' + , transitionDuration: '300ms' + }, parsedAttrs) - var options = angular.extend({ - isInitLayout: false, - itemSelector: '.packery-item', - columnWidth: '.packery-item', - transitionDuration: '300ms' - }, parsedAttrs) + var pckry = new PackeryService(container, options) + pckry.on('layoutComplete', onLayoutComplete) + pckry.bindResize() + bindDraggable() - var pckry = new PackeryService(container, options) - pckry.on('layoutComplete', onLayoutComplete) - pckry.bindResize() - bindDraggable() + $timeout(function() { + pckry.layout() + }, 0) + $timeout(function() { + pckry.layout() + }, 100) - $timeout(function() { - pckry.layout() - }, 0) - $timeout(function() { - pckry.layout() - }, 100) + function bindDraggable() { + if (options.draggable) { + var draggableOptions = {} + if (options.draggableHandle) { + draggableOptions.handle = options.draggableHandle + } + var itemElems = pckry.getItemElements() + for (var i = 0, len = itemElems.length; i < len; ++i) { + var elem = itemElems[i] + var draggie = new DraggabillyService(elem, draggableOptions) + pckry.bindDraggabillyEvents(draggie) + } + } + } - function bindDraggable() { - if (options.draggable) { - var draggableOptions = {} - if (options.draggableHandle) { - draggableOptions.handle = options.draggableHandle - } - var itemElems = pckry.getItemElements() - for (var i = 0, len = itemElems.length; i < len; ++i) { - var elem = itemElems[i] - var draggie = new DraggabillyService(elem, draggableOptions) - pckry.bindDraggabillyEvents(draggie) - } - } - } - - function onLayoutComplete() { - return true - } + function onLayoutComplete() { + return true + } - function onPanelsResized() { - pckry.layout() - } + function onPanelsResized() { + pckry.layout() + } - scope.$on('panelsResized', _.throttle(onPanelsResized, 300)) + scope.$on('panelsResized', _.throttle(onPanelsResized, 300)) - scope.$on('$destroy', function() { - pckry.unbindResize() - pckry.off('layoutComplete', onLayoutComplete) - pckry.destroy() - }) + scope.$on('$destroy', function() { + pckry.unbindResize() + pckry.off('layoutComplete', onLayoutComplete) + pckry.destroy() + }) + } } - } } diff --git a/res/app/components/stf/angular-packery/angular-packery-spec.js b/res/app/components/stf/angular-packery/angular-packery-spec.js index 0ca65ccd66..e0f9c68be1 100644 --- a/res/app/components/stf/angular-packery/angular-packery-spec.js +++ b/res/app/components/stf/angular-packery/angular-packery-spec.js @@ -1,17 +1,16 @@ describe('angularPackery', function() { + beforeEach(angular.mock.module(require('./').name)) - beforeEach(angular.mock.module(require('./').name)) + var scope, compile - var scope, compile + beforeEach(inject(function($rootScope, $compile) { + scope = $rootScope.$new() + compile = $compile + })) - beforeEach(inject(function($rootScope, $compile) { - scope = $rootScope.$new() - compile = $compile - })) + it('should ...', function() { - it('should ...', function() { - - /* + /* To test your directive, you need to create some html that would use your directive, send that through compile() then compare the results. @@ -19,5 +18,5 @@ describe('angularPackery', function() { expect(element.text()).toBe('hello, world'); */ - }) + }) }) diff --git a/res/app/components/stf/angular-packery/index.js b/res/app/components/stf/angular-packery/index.js index 40cf575ae4..a8544e0610 100644 --- a/res/app/components/stf/angular-packery/index.js +++ b/res/app/components/stf/angular-packery/index.js @@ -6,9 +6,9 @@ require('packery/js/item.js') var packery = require('packery/js/packery.js') module.exports = angular.module('stf.angular-packery', [ - require('stf/angular-draggabilly').name + require('stf/angular-draggabilly').name ]) - .factory('PackeryService', function() { - return packery - }) - .directive('angularPackery', require('./angular-packery-directive')) + .factory('PackeryService', function() { + return packery + }) + .directive('angularPackery', require('./angular-packery-directive')) diff --git a/res/app/components/stf/app-state/app-state-provider.js b/res/app/components/stf/app-state/app-state-provider.js index ef74f2cc21..8deb518dfc 100644 --- a/res/app/components/stf/app-state/app-state-provider.js +++ b/res/app/components/stf/app-state/app-state-provider.js @@ -1,27 +1,27 @@ module.exports = function AppStateProvider() { - var values = { - config: { - websocketUrl: '' - }, - user: { - settings: {} - }, - device: { - platform: '' + var values = { + config: { + websocketUrl: '' + } + , user: { + settings: {} + } + , device: { + platform: '' + } } - } - /* global GLOBAL_APPSTATE:false */ - if (typeof GLOBAL_APPSTATE !== 'undefined') { - values = angular.extend(values, GLOBAL_APPSTATE) - } + /* global GLOBAL_APPSTATE:false */ + if (typeof GLOBAL_APPSTATE !== 'undefined') { + values = angular.extend(values, GLOBAL_APPSTATE) + } - return { - set: function(constants) { - angular.extend(values, constants) - }, - $get: function() { - return values + return { + set: function(constants) { + angular.extend(values, constants) + } + , $get: function() { + return values + } } - } } diff --git a/res/app/components/stf/app-state/index.js b/res/app/components/stf/app-state/index.js index 937801a6bb..54741058d0 100644 --- a/res/app/components/stf/app-state/index.js +++ b/res/app/components/stf/app-state/index.js @@ -1,3 +1,3 @@ module.exports = angular.module('stf.app-state', [ ]) - .provider('AppState', require('./app-state-provider.js')) + .provider('AppState', require('./app-state-provider.js')) diff --git a/res/app/components/stf/basic-mode/basic-mode-directive.js b/res/app/components/stf/basic-mode/basic-mode-directive.js index 029e7d0098..df4698926f 100644 --- a/res/app/components/stf/basic-mode/basic-mode-directive.js +++ b/res/app/components/stf/basic-mode/basic-mode-directive.js @@ -1,9 +1,9 @@ module.exports = function basicModeDirective($rootScope, BrowserInfo) { - return { - restrict: 'AE', - link: function(scope, element) { - $rootScope.basicMode = !!BrowserInfo.mobile - element.addClass('basic-mode') + return { + restrict: 'AE' + , link: function(scope, element) { + $rootScope.basicMode = !!BrowserInfo.mobile + element.addClass('basic-mode') + } } - } } diff --git a/res/app/components/stf/basic-mode/basic-mode-spec.js b/res/app/components/stf/basic-mode/basic-mode-spec.js index a2bc5613e0..1396d57112 100644 --- a/res/app/components/stf/basic-mode/basic-mode-spec.js +++ b/res/app/components/stf/basic-mode/basic-mode-spec.js @@ -1,17 +1,16 @@ describe('basicMode', function() { + beforeEach(angular.mock.module(require('./').name)) - beforeEach(angular.mock.module(require('./').name)) + var scope, compile - var scope, compile + beforeEach(inject(function($rootScope, $compile) { + scope = $rootScope.$new() + compile = $compile + })) - beforeEach(inject(function($rootScope, $compile) { - scope = $rootScope.$new() - compile = $compile - })) + it('should ...', function() { - it('should ...', function() { - - /* + /* To test your directive, you need to create some html that would use your directive, send that through compile() then compare the results. @@ -19,5 +18,5 @@ describe('basicMode', function() { expect(element.text()).toBe('hello, world'); */ - }) + }) }) diff --git a/res/app/components/stf/basic-mode/index.js b/res/app/components/stf/basic-mode/index.js index 160f60401a..b0f45dccfc 100644 --- a/res/app/components/stf/basic-mode/index.js +++ b/res/app/components/stf/basic-mode/index.js @@ -2,4 +2,4 @@ require('./basic-mode.css') module.exports = angular.module('stf.basic-mode', [ ]) - .directive('basicMode', require('./basic-mode-directive')) + .directive('basicMode', require('./basic-mode-directive')) diff --git a/res/app/components/stf/browser-info/browser-info-service.js b/res/app/components/stf/browser-info/browser-info-service.js index e1ec2ab790..015cbad2f4 100644 --- a/res/app/components/stf/browser-info/browser-info-service.js +++ b/res/app/components/stf/browser-info/browser-info-service.js @@ -1,67 +1,67 @@ // NOTE: Most of the detection stuff is from Modernizr 3.0 module.exports = function BrowserInfoServiceFactory() { - var service = {} + var service = {} - function createElement() { - return document.createElement.apply(document, arguments) - } + function createElement() { + return document.createElement.apply(document, arguments) + } - function addTest(key, test) { - service[key] = (typeof test == 'function') ? test() : test - } + function addTest(key, test) { + service[key] = (typeof test == 'function') ? test() : test + } - addTest('touch', function() { - return ('ontouchstart' in window) || window.DocumentTouch && + addTest('touch', function() { + return ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch - }) + }) - addTest('retina', function() { - var mediaQuery = '(-webkit-min-device-pixel-ratio: 1.5), ' + + addTest('retina', function() { + var mediaQuery = '(-webkit-min-device-pixel-ratio: 1.5), ' + '(min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), ' + '(min-resolution: 1.5dppx)' - if (window.devicePixelRatio > 1) { - return true - } - return !!(window.matchMedia && window.matchMedia(mediaQuery).matches) - }) + if (window.devicePixelRatio > 1) { + return true + } + return !!(window.matchMedia && window.matchMedia(mediaQuery).matches) + }) - addTest('small', function() { - var windowWidth = window.screen.width < window.outerWidth ? - window.screen.width : window.outerWidth - return windowWidth < 800 - }) + addTest('small', function() { + var windowWidth = window.screen.width < window.outerWidth ? + window.screen.width : window.outerWidth + return windowWidth < 800 + }) - addTest('os', function() { - var ua = navigator.userAgent - if (ua.match(/Android/i)) { - return 'android' - } - else if (ua.match(/iPhone|iPad|iPod/i)) { - return 'ios' - } - else { - return 'pc' - } - }) + addTest('os', function() { + var ua = navigator.userAgent + if (ua.match(/Android/i)) { + return 'android' + } + else if (ua.match(/iPhone|iPad|iPod/i)) { + return 'ios' + } + else { + return 'pc' + } + }) - addTest('mobile', function() { - return !!(service.small && service.touch && (service.os !== 'pc')) - }) + addTest('mobile', function() { + return !!(service.small && service.touch && (service.os !== 'pc')) + }) - addTest('webgl', function() { - var canvas = createElement('canvas') - if ('supportsContext' in canvas) { - return canvas.supportsContext('webgl') || + addTest('webgl', function() { + var canvas = createElement('canvas') + if ('supportsContext' in canvas) { + return canvas.supportsContext('webgl') || canvas.supportsContext('experimental-webgl') - } - return !!window.WebGLRenderingContext - }) + } + return !!window.WebGLRenderingContext + }) - addTest('ua', navigator.userAgent) + addTest('ua', navigator.userAgent) - addTest('devicemotion', 'DeviceMotionEvent' in window) + addTest('devicemotion', 'DeviceMotionEvent' in window) - addTest('deviceorientation', 'DeviceOrientationEvent' in window) + addTest('deviceorientation', 'DeviceOrientationEvent' in window) - return service + return service } diff --git a/res/app/components/stf/browser-info/browser-info-spec.js b/res/app/components/stf/browser-info/browser-info-spec.js index de93ba8215..327d4a12fa 100644 --- a/res/app/components/stf/browser-info/browser-info-spec.js +++ b/res/app/components/stf/browser-info/browser-info-spec.js @@ -1,11 +1,9 @@ describe('BrowserInfo', function() { + beforeEach(angular.mock.module(require('./').name)) - beforeEach(angular.mock.module(require('./').name)) + it('should ...', inject(function() { - it('should ...', inject(function() { - - // expect(BrowserInfo.doSomething()).toEqual('something') - - })) + // expect(BrowserInfo.doSomething()).toEqual('something') + })) }) diff --git a/res/app/components/stf/browser-info/index.js b/res/app/components/stf/browser-info/index.js index 7dbfac3c53..f51454ecc6 100644 --- a/res/app/components/stf/browser-info/index.js +++ b/res/app/components/stf/browser-info/index.js @@ -1,4 +1,4 @@ module.exports = angular.module('stf.browser-info', [ ]) - .factory('BrowserInfo', require('./browser-info-service')) + .factory('BrowserInfo', require('./browser-info-service')) diff --git a/res/app/components/stf/column-choice/column-choice-directive.js b/res/app/components/stf/column-choice/column-choice-directive.js index a828cf6798..48874026a2 100644 --- a/res/app/components/stf/column-choice/column-choice-directive.js +++ b/res/app/components/stf/column-choice/column-choice-directive.js @@ -3,18 +3,18 @@ **/ module.exports = function() { - return { - restrict: 'E', - scope: { - buttonStyle: '@?', - columnData: '=', - resetData: '&' - }, - template: require('./column-choice.pug'), - link: function (scope) { - if (!localStorage.getItem('deviceData')) { - scope.resetData() - } + return { + restrict: 'E' + , scope: { + buttonStyle: '@?' + , columnData: '=' + , resetData: '&' + } + , template: require('./column-choice.pug') + , link: function(scope) { + if (!localStorage.getItem('deviceData')) { + scope.resetData() + } + } } - } } diff --git a/res/app/components/stf/column-choice/index.js b/res/app/components/stf/column-choice/index.js index 33a2ddd67f..5f3bc8d20e 100644 --- a/res/app/components/stf/column-choice/index.js +++ b/res/app/components/stf/column-choice/index.js @@ -5,8 +5,8 @@ require('./column-choice.css') module.exports = angular.module('stf.column-choice', [ - require('stf/common-ui').name + require('stf/common-ui').name ]) - .directive('stfColumnChoice', require('./column-choice-directive')) + .directive('stfColumnChoice', require('./column-choice-directive')) diff --git a/res/app/components/stf/common-ui/badge-icon/badge-icon-directive.js b/res/app/components/stf/common-ui/badge-icon/badge-icon-directive.js index 919f3beb45..32caf4274c 100644 --- a/res/app/components/stf/common-ui/badge-icon/badge-icon-directive.js +++ b/res/app/components/stf/common-ui/badge-icon/badge-icon-directive.js @@ -1,11 +1,11 @@ module.exports = function badgeIconDirective() { - return { - restrict: 'EA', - replace: true, - scope: { - }, - template: require('./badge-icon.pug'), - link: function() { + return { + restrict: 'EA' + , replace: true + , scope: { + } + , template: require('./badge-icon.pug') + , link: function() { + } } - } } diff --git a/res/app/components/stf/common-ui/badge-icon/badge-icon-spec.js b/res/app/components/stf/common-ui/badge-icon/badge-icon-spec.js index b1b748597b..e29c7c70d3 100644 --- a/res/app/components/stf/common-ui/badge-icon/badge-icon-spec.js +++ b/res/app/components/stf/common-ui/badge-icon/badge-icon-spec.js @@ -1,17 +1,16 @@ describe('badgeIcon', function() { + beforeEach(angular.mock.module(require('./').name)) - beforeEach(angular.mock.module(require('./').name)) + var scope, compile - var scope, compile + beforeEach(inject(function($rootScope, $compile) { + scope = $rootScope.$new() + compile = $compile + })) - beforeEach(inject(function($rootScope, $compile) { - scope = $rootScope.$new() - compile = $compile - })) + it('should ...', function() { - it('should ...', function() { - - /* + /* To test your directive, you need to create some html that would use your directive, send that through compile() then compare the results. @@ -19,5 +18,5 @@ describe('badgeIcon', function() { expect(element.text()).toBe('hello, world'); */ - }) + }) }) diff --git a/res/app/components/stf/common-ui/badge-icon/index.js b/res/app/components/stf/common-ui/badge-icon/index.js index 506df4ece0..459949e568 100644 --- a/res/app/components/stf/common-ui/badge-icon/index.js +++ b/res/app/components/stf/common-ui/badge-icon/index.js @@ -3,4 +3,4 @@ require('./badge-icon.css') module.exports = angular.module('stf.badge-icon', [ ]) - .directive('badgeIcon', require('./badge-icon-directive')) + .directive('badgeIcon', require('./badge-icon-directive')) diff --git a/res/app/components/stf/common-ui/blur-element/blur-element-directive.js b/res/app/components/stf/common-ui/blur-element/blur-element-directive.js index ed1f3970ca..569ab04bd9 100644 --- a/res/app/components/stf/common-ui/blur-element/blur-element-directive.js +++ b/res/app/components/stf/common-ui/blur-element/blur-element-directive.js @@ -1,32 +1,33 @@ module.exports = function blurElementDirective( - $parse, - $rootScope, - $timeout, + $parse, + $rootScope, + $timeout, ) { - return { - restrict: 'A', - link: function(scope, element, attrs) { - var model = $parse(attrs.blurElement) + return { + restrict: 'A' + , link: function(scope, element, attrs) { + var model = $parse(attrs.blurElement) - scope.$watch(model, function(value) { - if (value === true) { - $timeout(function() { - element[0].blur() - }) - } - }) + scope.$watch(model, function(value) { + if (value === true) { + $timeout(function() { + element[0].blur() + }) + } + }) - element.bind('blur', function() { - if(!$rootScope.$$phase) { - scope.$apply(() => { - model.assign(scope, false) - }) - } else { - scope.$applyAsync(() => { - model.assign(scope, false) - }) + element.bind('blur', function() { + if(!$rootScope.$$phase) { + scope.$apply(() => { + model.assign(scope, false) + }) + } + else { + scope.$applyAsync(() => { + model.assign(scope, false) + }) + } + }) } - }) } - } } diff --git a/res/app/components/stf/common-ui/blur-element/blur-element-spec.js b/res/app/components/stf/common-ui/blur-element/blur-element-spec.js index 96ddef5f97..4f8d8d794c 100644 --- a/res/app/components/stf/common-ui/blur-element/blur-element-spec.js +++ b/res/app/components/stf/common-ui/blur-element/blur-element-spec.js @@ -1,17 +1,16 @@ describe('blurElement', function() { + beforeEach(angular.mock.module(require('./').name)) - beforeEach(angular.mock.module(require('./').name)) + var scope, compile - var scope, compile + beforeEach(inject(function($rootScope, $compile) { + scope = $rootScope.$new() + compile = $compile + })) - beforeEach(inject(function($rootScope, $compile) { - scope = $rootScope.$new() - compile = $compile - })) + it('should ...', function() { - it('should ...', function() { - - /* + /* To test your directive, you need to create some html that would use your directive, send that through compile() then compare the results. @@ -19,5 +18,5 @@ describe('blurElement', function() { expect(element.text()).toBe('hello, world'); */ - }) + }) }) diff --git a/res/app/components/stf/common-ui/blur-element/index.js b/res/app/components/stf/common-ui/blur-element/index.js index 82ef62d0aa..1e72904060 100644 --- a/res/app/components/stf/common-ui/blur-element/index.js +++ b/res/app/components/stf/common-ui/blur-element/index.js @@ -1,4 +1,4 @@ module.exports = angular.module('stf.blur-element', [ ]) - .directive('blurElement', require('./blur-element-directive')) + .directive('blurElement', require('./blur-element-directive')) diff --git a/res/app/components/stf/common-ui/clear-button/clear-button-directive.js b/res/app/components/stf/common-ui/clear-button/clear-button-directive.js index e35f74eca6..a85e7af705 100644 --- a/res/app/components/stf/common-ui/clear-button/clear-button-directive.js +++ b/res/app/components/stf/common-ui/clear-button/clear-button-directive.js @@ -1,8 +1,8 @@ module.exports = function clearButtonDirective() { - return { - restrict: 'EA', - replace: true, - scope: {}, - template: require('./clear-button.pug') - } + return { + restrict: 'EA' + , replace: true + , scope: {} + , template: require('./clear-button.pug') + } } diff --git a/res/app/components/stf/common-ui/clear-button/clear-button-spec.js b/res/app/components/stf/common-ui/clear-button/clear-button-spec.js index a636179bb2..d8f1fc7131 100644 --- a/res/app/components/stf/common-ui/clear-button/clear-button-spec.js +++ b/res/app/components/stf/common-ui/clear-button/clear-button-spec.js @@ -1,22 +1,20 @@ describe('clearButton', function() { + beforeEach(angular.mock.module(require('./').name)) - beforeEach(angular.mock.module(require('./').name)) + var scope, compile - var scope, compile + beforeEach(inject(function($rootScope, $compile) { + scope = $rootScope.$new() + compile = $compile + })) - beforeEach(inject(function($rootScope, $compile) { - scope = $rootScope.$new() - compile = $compile - })) - - it('should display a text label', function() { - var element = compile('')(scope) - expect(element.find('span').text()).toBe('Clear') - }) - - it('should display a trash icon', function() { - var element = compile('')(scope) - expect(element.find('i')[0].getAttribute('class')).toMatch('fa-trash-o') - }) + it('should display a text label', function() { + var element = compile('')(scope) + expect(element.find('span').text()).toBe('Clear') + }) + it('should display a trash icon', function() { + var element = compile('')(scope) + expect(element.find('i')[0].getAttribute('class')).toMatch('fa-trash-o') + }) }) diff --git a/res/app/components/stf/common-ui/clear-button/index.js b/res/app/components/stf/common-ui/clear-button/index.js index fb4023b921..4ccdd1966e 100644 --- a/res/app/components/stf/common-ui/clear-button/index.js +++ b/res/app/components/stf/common-ui/clear-button/index.js @@ -1,2 +1,2 @@ module.exports = angular.module('stf.clear-button', []) - .directive('clearButton', require('./clear-button-directive')) + .directive('clearButton', require('./clear-button-directive')) diff --git a/res/app/components/stf/common-ui/counter/counter-directive.js b/res/app/components/stf/common-ui/counter/counter-directive.js index 153dd2e621..b74d312afa 100644 --- a/res/app/components/stf/common-ui/counter/counter-directive.js +++ b/res/app/components/stf/common-ui/counter/counter-directive.js @@ -1,62 +1,60 @@ module.exports = function counterDirective($timeout) { - return { - replace: false, - scope: true, - link: function(scope, element, attrs) { - var el = element[0] - var num, refreshInterval, duration, steps, step, countTo, increment - - var calculate = function() { - refreshInterval = 32 - step = 0 - scope.timoutId = null - countTo = parseInt(attrs.countTo, 10) || 0 - scope.value = parseInt(attrs.countFrom, 10) || 0 - duration = parseFloat(attrs.duration) || 0 - - steps = Math.ceil(duration / refreshInterval) - - increment = ((countTo - scope.value) / steps) - num = scope.value - } - - var tick = function() { - scope.timoutId = $timeout(function() { - num += increment - step++ - if (step >= steps) { - $timeout.cancel(scope.timoutId) - num = countTo - el.innerText = countTo - } - else { - el.innerText = Math.round(num) - tick() - } - }, refreshInterval) - - } - - var start = function() { - if (scope.timoutId) { - $timeout.cancel(scope.timoutId) + return { + replace: false + , scope: true + , link: function(scope, element, attrs) { + var el = element[0] + var num, refreshInterval, duration, steps, step, countTo, increment + + var calculate = function() { + refreshInterval = 32 + step = 0 + scope.timoutId = null + countTo = parseInt(attrs.countTo, 10) || 0 + scope.value = parseInt(attrs.countFrom, 10) || 0 + duration = parseFloat(attrs.duration) || 0 + + steps = Math.ceil(duration / refreshInterval) + + increment = ((countTo - scope.value) / steps) + num = scope.value + } + + var tick = function() { + scope.timoutId = $timeout(function() { + num += increment + step++ + if (step >= steps) { + $timeout.cancel(scope.timoutId) + num = countTo + el.innerText = countTo + } + else { + el.innerText = Math.round(num) + tick() + } + }, refreshInterval) + } + + var start = function() { + if (scope.timoutId) { + $timeout.cancel(scope.timoutId) + } + calculate() + tick() + } + + attrs.$observe('countTo', function(val) { + if (val) { + start() + } + }) + + attrs.$observe('countFrom', function() { + start() + }) + + return true } - calculate() - tick() - } - - attrs.$observe('countTo', function(val) { - if (val) { - start() - } - }) - - attrs.$observe('countFrom', function() { - start() - }) - - return true } - } - } diff --git a/res/app/components/stf/common-ui/counter/counter-spec.js b/res/app/components/stf/common-ui/counter/counter-spec.js index d914ff03ed..34f0e35923 100644 --- a/res/app/components/stf/common-ui/counter/counter-spec.js +++ b/res/app/components/stf/common-ui/counter/counter-spec.js @@ -1,17 +1,16 @@ describe('counter', function() { + beforeEach(angular.mock.module(require('./').name)) - beforeEach(angular.mock.module(require('./').name)) + var scope, compile - var scope, compile + beforeEach(inject(function($rootScope, $compile) { + scope = $rootScope.$new() + compile = $compile + })) - beforeEach(inject(function($rootScope, $compile) { - scope = $rootScope.$new() - compile = $compile - })) + it('should ...', function() { - it('should ...', function() { - - /* + /* To test your directive, you need to create some html that would use your directive, send that through compile() then compare the results. @@ -19,5 +18,5 @@ describe('counter', function() { expect(element.text()).toBe('hello, world'); */ - }) + }) }) diff --git a/res/app/components/stf/common-ui/counter/index.js b/res/app/components/stf/common-ui/counter/index.js index 9b1d60eed7..c73fbc12b4 100644 --- a/res/app/components/stf/common-ui/counter/index.js +++ b/res/app/components/stf/common-ui/counter/index.js @@ -1,4 +1,4 @@ module.exports = angular.module('stf.counter', [ ]) - .directive('countFrom', require('./counter-directive')) + .directive('countFrom', require('./counter-directive')) diff --git a/res/app/components/stf/common-ui/enable-autofill/enable-autofill-directive.js b/res/app/components/stf/common-ui/enable-autofill/enable-autofill-directive.js index 6b58f10a67..e619563244 100644 --- a/res/app/components/stf/common-ui/enable-autofill/enable-autofill-directive.js +++ b/res/app/components/stf/common-ui/enable-autofill/enable-autofill-directive.js @@ -1,54 +1,53 @@ module.exports = function enableAutofillDirective($rootElement, $cookies) { - return { - restrict: 'A', - compile: function compile(tElement, tAttrs) { - - // Creates hidden iFrame for auto-fill forms if there isn't one already - if ($rootElement.find('iframe').attr('name') !== '_autofill') { - $rootElement.append(angular.element( - '