@antelopejs/dms 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +36 -26
- package/bin/control/base.mjs +9 -4
- package/bin/control/browser-client.mjs +24 -5
- package/bin/control/browser-wire.mjs +41 -0
- package/bin/control/browser-wire.test.mjs +96 -0
- package/bin/control/browser.mjs +78 -27
- package/bin/control/commands-stack.mjs +58 -8
- package/bin/control/commands-verify.mjs +47 -1
- package/bin/control/doctor.mjs +49 -4
- package/bin/control/failure.mjs +9 -0
- package/bin/control/journey.mjs +158 -30
- package/bin/control/journey.test.mjs +61 -0
- package/bin/control/json.mjs +229 -0
- package/bin/control/json.test.mjs +130 -0
- package/bin/control/mail.mjs +57 -7
- package/bin/control/mail.test.mjs +55 -0
- package/bin/control/mongo.mjs +205 -27
- package/bin/control/mongo.test.mjs +298 -0
- package/bin/control/options.mjs +33 -0
- package/bin/control/processes.mjs +24 -2
- package/bin/control/renderer.mjs +39 -0
- package/bin/control/renderer.test.mjs +324 -0
- package/bin/control/run-state.mjs +119 -6
- package/bin/control/run-state.test.mjs +171 -0
- package/bin/control/session.mjs +30 -14
- package/bin/control/stack.mjs +95 -24
- package/bin/control/tab.mjs +117 -18
- package/bin/control/verify/behaviour.mjs +44 -6
- package/bin/control/verify/behaviour.test.mjs +130 -22
- package/bin/control/verify/decode.mjs +220 -0
- package/bin/control/verify/decode.test.mjs +245 -0
- package/bin/control/verify/doors.mjs +1 -1
- package/bin/control/verify/doors.test.mjs +40 -0
- package/bin/control/verify/emit.mjs +135 -9
- package/bin/control/verify/formal.mjs +61 -10
- package/bin/control/verify/formal.test.mjs +19 -3
- package/bin/control/verify/judgement.mjs +22 -3
- package/bin/control/verify/map.mjs +25 -5
- package/bin/control/verify/publish.mjs +20 -14
- package/bin/control/verify/seeded.mjs +65 -16
- package/bin/control/verify/skill.mjs +27 -6
- package/bin/control/verify/types.mjs +29 -0
- package/bin/control/verify/verdict.mjs +72 -7
- package/bin/dms-control.mjs +8 -28
- package/checks/api-error-presentation.d.mts +61 -0
- package/dist/attachments/access.js +1 -1
- package/dist/attachments/access.js.map +1 -1
- package/dist/attachments/fields.d.ts +2 -2
- package/dist/attachments/fields.js +26 -13
- package/dist/attachments/fields.js.map +1 -1
- package/dist/attachments/index.d.ts +1 -0
- package/dist/attachments/index.js +1 -0
- package/dist/attachments/index.js.map +1 -1
- package/dist/attachments/read.js.map +1 -1
- package/dist/attachments/save.d.ts +1 -1
- package/dist/attachments/save.js +49 -17
- package/dist/attachments/save.js.map +1 -1
- package/dist/attachments/table-view.d.ts +1 -2
- package/dist/attachments/table-view.js +23 -13
- package/dist/attachments/table-view.js.map +1 -1
- package/dist/automation/events.d.ts +10 -10
- package/dist/automation/index.js +3 -4
- package/dist/automation/index.js.map +1 -1
- package/dist/automation/send-notification.d.ts +2 -2
- package/dist/automation/send-notification.js +4 -6
- package/dist/automation/send-notification.js.map +1 -1
- package/dist/crons/cleanup-user-invites.js.map +1 -1
- package/dist/crons/sweep-expired-sessions.js.map +1 -1
- package/dist/crons/sweep-stale-exports.js.map +1 -1
- package/dist/db/guard-row.d.ts +2 -3
- package/dist/db/guard-row.js +1 -2
- package/dist/db/guard-row.js.map +1 -1
- package/dist/db/models/attachments.model.d.ts +7 -5
- package/dist/db/models/setupToken.model.d.ts +7 -5
- package/dist/db/models/signInAttempts.model.d.ts +7 -5
- package/dist/db/models/systemState.model.d.ts +7 -5
- package/dist/db/models/userEmailChanges.model.d.ts +7 -5
- package/dist/db/models/userKnownDevices.model.d.ts +7 -5
- package/dist/db/models/userNotificationPreferences.model.d.ts +7 -5
- package/dist/db/models/userNotifications.model.d.ts +9 -6
- package/dist/db/models/userNotifications.model.js.map +1 -1
- package/dist/dev/module-update-notifications.js.map +1 -1
- package/dist/hooks/internal/sign-in-monitor.js +6 -6
- package/dist/hooks/internal/sign-in-monitor.js.map +1 -1
- package/dist/implementations/dms/page/controller.d.ts +2 -2
- package/dist/implementations/dms/page/controller.js +3 -1
- package/dist/implementations/dms/page/controller.js.map +1 -1
- package/dist/implementations/dms/page/dynamic-menu.js +3 -2
- package/dist/implementations/dms/page/dynamic-menu.js.map +1 -1
- package/dist/implementations/dms/page/notifications.d.ts +6 -2
- package/dist/implementations/dms/page/notifications.js +1 -0
- package/dist/implementations/dms/page/notifications.js.map +1 -1
- package/dist/implementations/dms/page/preview-pages.js +6 -3
- package/dist/implementations/dms/page/preview-pages.js.map +1 -1
- package/dist/implementations/dms/page/registry.js +10 -4
- package/dist/implementations/dms/page/registry.js.map +1 -1
- package/dist/implementations/dms/permissions.js +12 -12
- package/dist/implementations/dms/permissions.js.map +1 -1
- package/dist/implementations/dms-base/bulk-selection.d.ts +3 -4
- package/dist/implementations/dms-base/bulk-selection.js +18 -6
- package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
- package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
- package/dist/implementations/dms-base/footer-summary.js +44 -27
- package/dist/implementations/dms-base/footer-summary.js.map +1 -1
- package/dist/implementations/dms-base/row-rule-operand.d.ts +6 -0
- package/dist/implementations/dms-base/row-rule-operand.js +41 -0
- package/dist/implementations/dms-base/row-rule-operand.js.map +1 -0
- package/dist/implementations/dms-base/search-route.d.ts +9 -13
- package/dist/implementations/dms-base/search-route.js +7 -6
- package/dist/implementations/dms-base/search-route.js.map +1 -1
- package/dist/implementations/dms-base/services.d.ts +1 -0
- package/dist/implementations/dms-base/services.js +1 -0
- package/dist/implementations/dms-base/services.js.map +1 -1
- package/dist/implementations/dms-base/table-view.d.ts +5 -6
- package/dist/implementations/dms-base/table-view.js +17 -6
- package/dist/implementations/dms-base/table-view.js.map +1 -1
- package/dist/implementations/dms-html-render/index.js +16 -6
- package/dist/implementations/dms-html-render/index.js.map +1 -1
- package/dist/implementations/dms-notifications/delivery-guard.d.ts +3 -2
- package/dist/implementations/dms-notifications/delivery-guard.js.map +1 -1
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pages/public/preview/block-types.d.ts +1 -2
- package/dist/pages/public/preview/block-types.js.map +1 -1
- package/dist/pages/settings/overview/account-summary.js +4 -1
- package/dist/pages/settings/overview/account-summary.js.map +1 -1
- package/dist/pages/settings/users/account-activity-feed.js +3 -2
- package/dist/pages/settings/users/account-activity-feed.js.map +1 -1
- package/dist/pages/settings/users/account-credentials.d.ts +1 -1
- package/dist/pages/settings/users/account-credentials.js +1 -2
- package/dist/pages/settings/users/account-credentials.js.map +1 -1
- package/dist/pages/settings/users/account-data-store.d.ts +1 -1
- package/dist/pages/settings/users/account-data-store.js +1 -3
- package/dist/pages/settings/users/account-data-store.js.map +1 -1
- package/dist/pages/settings/users/email-change.d.ts +2 -2
- package/dist/pages/settings/users/email-change.js +2 -3
- package/dist/pages/settings/users/email-change.js.map +1 -1
- package/dist/pages/settings/users/invite-extension-routes.d.ts +2 -2
- package/dist/pages/settings/users/invite-extension-routes.js +11 -4
- package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
- package/dist/pages/settings/users/invites.d.ts +2 -2
- package/dist/pages/settings/users/member-invite-batch.d.ts +2 -2
- package/dist/pages/settings/users/member-invite-batch.js +7 -3
- package/dist/pages/settings/users/member-invite-batch.js.map +1 -1
- package/dist/pages/settings/users/member-roles-field.js +1 -4
- package/dist/pages/settings/users/member-roles-field.js.map +1 -1
- package/dist/pages/settings/users/members.d.ts +2 -3
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.d.ts +2 -3
- package/dist/pages/settings/users/notifications.js +2 -1
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/pages/settings/users/profile-helpers.d.ts +0 -1
- package/dist/pages/settings/users/profile-helpers.js +0 -17
- package/dist/pages/settings/users/profile-helpers.js.map +1 -1
- package/dist/pages/settings/users/profile.d.ts +3 -4
- package/dist/pages/settings/users/profile.js +12 -12
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/region.d.ts +3 -4
- package/dist/pages/settings/users/region.js.map +1 -1
- package/dist/pages/settings/users/regional-preferences.d.ts +8 -1
- package/dist/pages/settings/users/regional-preferences.js +10 -6
- package/dist/pages/settings/users/regional-preferences.js.map +1 -1
- package/dist/pages/settings/users/role-preview.js +3 -2
- package/dist/pages/settings/users/role-preview.js.map +1 -1
- package/dist/pages/settings/users/roles.d.ts +4 -5
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/pages/settings/users/security.d.ts +9 -12
- package/dist/pages/settings/users/security.js +11 -9
- package/dist/pages/settings/users/security.js.map +1 -1
- package/dist/pages/settings/users/two-factor-operations.d.ts +7 -7
- package/dist/pages/settings/users/two-factor-operations.js +5 -7
- package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
- package/dist/realtime/broker.js.map +1 -1
- package/dist/realtime/heartbeat.js.map +1 -1
- package/dist/realtime/redis-broker.js.map +1 -1
- package/dist/realtime/sessions.js.map +1 -1
- package/dist/realtime/sse.js +1 -2
- package/dist/realtime/sse.js.map +1 -1
- package/dist/routes/auth/forgot-password.d.ts +1 -1
- package/dist/routes/auth/forgot-password.js +1 -3
- package/dist/routes/auth/forgot-password.js.map +1 -1
- package/dist/routes/auth/login.d.ts +1 -1
- package/dist/routes/auth/login.js +1 -2
- package/dist/routes/auth/login.js.map +1 -1
- package/dist/routes/auth/logout.d.ts +1 -1
- package/dist/routes/auth/logout.js +1 -2
- package/dist/routes/auth/logout.js.map +1 -1
- package/dist/routes/auth/oauth/providers.js +30 -6
- package/dist/routes/auth/oauth/providers.js.map +1 -1
- package/dist/routes/auth/oauth/state.js +8 -5
- package/dist/routes/auth/oauth/state.js.map +1 -1
- package/dist/routes/auth/oauth/token-exchange.js +8 -1
- package/dist/routes/auth/oauth/token-exchange.js.map +1 -1
- package/dist/routes/auth/refresh.d.ts +1 -1
- package/dist/routes/auth/refresh.js +1 -2
- package/dist/routes/auth/refresh.js.map +1 -1
- package/dist/routes/auth/request-2fa-email.d.ts +1 -1
- package/dist/routes/auth/request-2fa-email.js +1 -2
- package/dist/routes/auth/request-2fa-email.js.map +1 -1
- package/dist/routes/auth/reset-password.d.ts +1 -1
- package/dist/routes/auth/reset-password.js +1 -3
- package/dist/routes/auth/reset-password.js.map +1 -1
- package/dist/routes/auth/session-handoff.d.ts +1 -1
- package/dist/routes/auth/session-handoff.js +1 -2
- package/dist/routes/auth/session-handoff.js.map +1 -1
- package/dist/routes/auth/signup.d.ts +1 -1
- package/dist/routes/auth/signup.js +1 -2
- package/dist/routes/auth/signup.js.map +1 -1
- package/dist/routes/auth/switch-tenant.d.ts +2 -3
- package/dist/routes/auth/switch-tenant.js +3 -2
- package/dist/routes/auth/switch-tenant.js.map +1 -1
- package/dist/routes/auth/validate-forgot-password-token.d.ts +1 -1
- package/dist/routes/auth/validate-forgot-password-token.js +1 -3
- package/dist/routes/auth/validate-forgot-password-token.js.map +1 -1
- package/dist/routes/auth/validate-invite-token.d.ts +1 -1
- package/dist/routes/auth/validate-invite-token.js +1 -3
- package/dist/routes/auth/validate-invite-token.js.map +1 -1
- package/dist/routes/auth/verify-2fa.d.ts +1 -1
- package/dist/routes/auth/verify-2fa.js +1 -2
- package/dist/routes/auth/verify-2fa.js.map +1 -1
- package/dist/routes/auth/verify-email.d.ts +1 -1
- package/dist/routes/auth/verify-email.js +1 -2
- package/dist/routes/auth/verify-email.js.map +1 -1
- package/dist/routes/auth.d.ts +14 -15
- package/dist/routes/auth.js +15 -13
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/files.d.ts +1 -2
- package/dist/routes/files.js.map +1 -1
- package/dist/routes/onboarding.d.ts +1 -2
- package/dist/routes/onboarding.js.map +1 -1
- package/dist/routes/realtime.js +5 -6
- package/dist/routes/realtime.js.map +1 -1
- package/dist/routes/store.d.ts +6 -7
- package/dist/routes/store.js +19 -13
- package/dist/routes/store.js.map +1 -1
- package/dist/store/catalog.d.ts +13 -5
- package/dist/store/catalog.js +19 -9
- package/dist/store/catalog.js.map +1 -1
- package/dist/store/compatibility.d.ts +1 -0
- package/dist/store/compatibility.js +24 -8
- package/dist/store/compatibility.js.map +1 -1
- package/dist/store/frontend-compatibility.js +15 -3
- package/dist/store/frontend-compatibility.js.map +1 -1
- package/dist/store/interfaces.js +35 -13
- package/dist/store/interfaces.js.map +1 -1
- package/dist/store/internal/frontend/frontend-modules.js +7 -3
- package/dist/store/internal/frontend/frontend-modules.js.map +1 -1
- package/dist/store/internal/frontend/frontend-ready.d.ts +2 -1
- package/dist/store/internal/frontend/frontend-ready.js +6 -4
- package/dist/store/internal/frontend/frontend-ready.js.map +1 -1
- package/dist/store/internal/frontend/manifest.js +6 -7
- package/dist/store/internal/frontend/manifest.js.map +1 -1
- package/dist/store/operations.js.map +1 -1
- package/dist/store/registry.js.map +1 -1
- package/dist/store/runtime.d.ts +13 -0
- package/dist/store/runtime.js +9 -1
- package/dist/store/runtime.js.map +1 -1
- package/dist/utils/admin-invite-email.js +3 -1
- package/dist/utils/admin-invite-email.js.map +1 -1
- package/dist/utils/email-subjects.js +3 -1
- package/dist/utils/email-subjects.js.map +1 -1
- package/dist/utils/export-jobs-run.js.map +1 -1
- package/dist/utils/export-jobs.js +3 -9
- package/dist/utils/export-jobs.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/own-value.d.ts +1 -0
- package/dist/utils/own-value.js +7 -0
- package/dist/utils/own-value.js.map +1 -0
- package/dist/utils/row-action-rule-evaluator.js +31 -35
- package/dist/utils/row-action-rule-evaluator.js.map +1 -1
- package/dist/utils/sign-in-country.js +4 -2
- package/dist/utils/sign-in-country.js.map +1 -1
- package/dist/utils/tenant-export-archive.js +9 -14
- package/dist/utils/tenant-export-archive.js.map +1 -1
- package/dist/utils/user-agent.js +2 -1
- package/dist/utils/user-agent.js.map +1 -1
- package/dist/validation/auth.schema.d.ts +10 -10
- package/dist/validation/regional-preferences.schema.d.ts +2 -2
- package/dist/validation/role-editor.schema.d.ts +1 -1
- package/frontend-vue/build/backend-boundary.ts +49 -18
- package/frontend-vue/dms.frontend.ts +8 -12
- package/frontend-vue/layers/dms-auth/server/accounts.ts +44 -3
- package/frontend-vue/layers/dms-auth/server/actions.ts +17 -4
- package/frontend-vue/layers/dms-auth/server/oauth.ts +42 -4
- package/frontend-vue/layers/dms-core/app/types/watch.ts +1 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +57 -6
- package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +7 -9
- package/frontend-vue/layers/dms-ui/app/entries/composables/form/registerFormFunctions.ts +1 -0
- package/frontend-vue/lazy-plugin.ts +18 -0
- package/frontend-vue/package.json +2 -2
- package/package.json +19 -13
- package/skills/dms-project/SKILL.md +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.16.0
|
|
4
|
+
|
|
5
|
+
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.15.1...v0.16.0)
|
|
6
|
+
|
|
7
|
+
### 📖 Documentation
|
|
8
|
+
|
|
9
|
+
- **readme:** Start the demos with npx @antelopejs/core ([#753](https://github.com/AntelopeJS/dms/pull/753))
|
|
10
|
+
- **readme:** Show the Meet and DMS films, with new GIFs ([1e3f7694](https://github.com/AntelopeJS/dms/commit/1e3f7694))
|
|
11
|
+
- **readme:** Link the read-only live demo ([#665](https://github.com/AntelopeJS/dms/pull/665))
|
|
12
|
+
|
|
13
|
+
### 🏡 Chore
|
|
14
|
+
|
|
15
|
+
- Land the cleanup campaign on main ([#761](https://github.com/AntelopeJS/dms/pull/761))
|
|
16
|
+
- **release:** @antelopejs/interface-dms v0.12.0 ([0e6b850d](https://github.com/AntelopeJS/dms/commit/0e6b850d))
|
|
17
|
+
|
|
18
|
+
### ❤️ Contributors
|
|
19
|
+
|
|
20
|
+
- Antony Rizzitelli ([@Upd4ting](http://github.com/Upd4ting))
|
|
21
|
+
|
|
3
22
|
## v0.15.1
|
|
4
23
|
|
|
5
24
|
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.15.0...v0.15.1)
|
package/README.md
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
<picture>
|
|
4
4
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/AntelopeJS/dms/main/packages/dms/frontend-vue/layers/dms-layout/public/images/antelope-logo/dark.svg">
|
|
5
|
-
<img alt="Antelope DMS" src="https://raw.githubusercontent.com/AntelopeJS/dms/main/packages/dms/frontend-vue/layers/dms-layout/public/images/antelope-logo/light.svg" height="72">
|
|
5
|
+
<img alt="Antelope DMS" src="https://raw.githubusercontent.com/AntelopeJS/dms/main/packages/dms/frontend-vue/layers/dms-layout/public/images/antelope-logo/light.svg" data-illustration height="72">
|
|
6
6
|
</picture>
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# `@antelopejs/dms`
|
|
9
9
|
|
|
10
|
-
<a href="https://www.npmjs.com/package/@antelopejs/dms"><img alt="npm" src="https://img.shields.io/npm/v/@antelopejs/dms?style=for-the-badge&labelColor=000000&color=000000"></a>
|
|
11
|
-
<a href="./LICENSE"><img alt="License" src="https://img.shields.io/npm/l/@antelopejs/dms?style=for-the-badge&labelColor=000000&color=000000"></a>
|
|
12
|
-
<a href="https://discord.gg/g4pFt6eXqu"><img alt="Discord" src="https://img.shields.io/badge/Discord-18181B?logo=discord&style=for-the-badge&color=000000"></a>
|
|
13
|
-
<a href="https://dms.antelopejs.com/docs"><img alt="Documentation" src="https://img.shields.io/badge/Docs-18181B?style=for-the-badge&color=000000"></a>
|
|
10
|
+
<a href="https://www.npmjs.com/package/@antelopejs/dms"><img alt="npm" src="https://img.shields.io/npm/v/@antelopejs/dms?style=for-the-badge&labelColor=000000&color=000000" data-illustration></a>
|
|
11
|
+
<a href="./LICENSE"><img alt="License" src="https://img.shields.io/npm/l/@antelopejs/dms?style=for-the-badge&labelColor=000000&color=000000" data-illustration></a>
|
|
12
|
+
<a href="https://discord.gg/g4pFt6eXqu"><img alt="Discord" src="https://img.shields.io/badge/Discord-18181B?logo=discord&style=for-the-badge&color=000000" data-illustration></a>
|
|
13
|
+
<a href="https://dms.antelopejs.com/docs"><img alt="Documentation" src="https://img.shields.io/badge/Docs-18181B?style=for-the-badge&color=000000" data-illustration></a>
|
|
14
14
|
|
|
15
15
|
**Admin dashboards for AntelopeJS, declared in TypeScript on the backend.**
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
-

|
|
22
22
|
|
|
23
23
|
## What it does
|
|
24
24
|
|
|
@@ -43,23 +43,21 @@ The DMS also needs an API module, a module implementing the database interface,
|
|
|
43
43
|
|
|
44
44
|
## Quick start
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Create a project from the `template-dms-demo` template, which loads the DMS, its companion modules and Helio, a demo back office for a solar installer. It needs the Node.js and pnpm releases the template pins, and a MongoDB on `localhost:27017`.
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
|
|
49
|
+
npx @antelopejs/core project init my-app --template dms-demo
|
|
50
50
|
cd my-app
|
|
51
|
-
pnpm install
|
|
52
|
-
pnpm --dir frontend-vue install
|
|
53
|
-
cat > .env <<EOF
|
|
54
|
-
DMS_API_BASE_URL=http://localhost:5010
|
|
55
|
-
DMS_SESSION_SECRET=$(openssl rand -hex 32)
|
|
56
|
-
EOF
|
|
57
|
-
pnpm exec ajs project modules install
|
|
58
51
|
pnpm dev # backend on http://localhost:5010
|
|
59
|
-
pnpm frontend:dev # dashboard on http://localhost:3001, in a second terminal
|
|
60
52
|
```
|
|
61
53
|
|
|
62
|
-
|
|
54
|
+
When the backend answers, start the dashboard in a second terminal, from the same folder:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm frontend:dev # dashboard on http://localhost:3001
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Open the address that the backend logs after `No owner exists yet`, and create the first administrator. The [Quickstart](https://dms.antelopejs.com/docs/getting-started/quickstart) walks through each step. A page is a class whose static fields are blocks:
|
|
63
61
|
|
|
64
62
|
```ts
|
|
65
63
|
import { PageController, RegisterPage, pagesCategory } from "@antelopejs/interface-dms/page";
|
|
@@ -77,14 +75,26 @@ export class SalesPage extends PageController("sales", {
|
|
|
77
75
|
|
|
78
76
|
## Compatibility
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
`@antelopejs/
|
|
78
|
+
<!-- antelopejs-docs-compat:start -->
|
|
79
|
+
|
|
80
|
+
| Package | Supported | Tested with |
|
|
81
|
+
| ------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
82
|
+
| `@antelopejs/core` | `>=1.17.0 <2` | `1.17.0` |
|
|
83
|
+
| `@antelopejs/interface-api` | `>=0.2.0 <1.0.0` | `0.2.0` |
|
|
84
|
+
| `@antelopejs/interface-api-util` | `>=0.3.0 <1.0.0` | `0.3.0` |
|
|
85
|
+
| `@antelopejs/interface-core` | `>=0.4.0 <1.0.0` | `0.4.0` |
|
|
86
|
+
| `@antelopejs/interface-data-api` | `>=0.6.0 <1.0.0` | `0.6.0` |
|
|
87
|
+
| `@antelopejs/interface-database` | `>=0.3.0 <1.0.0` | `0.3.0` |
|
|
88
|
+
| `@antelopejs/interface-database-decorators` | `>=0.2.0 <1.0.0` | `0.2.0` |
|
|
89
|
+
| `@antelopejs/interface-dms` | `>=0.12.0 <0.13.0` | `0.12.0` |
|
|
90
|
+
| `@antelopejs/interface-dms-automation` | `file:../../.campaign/tgz/antelopejs-interface-dms-automation-b4bc5d2.tgz` | `file:.campaign/tgz/antelopejs-interface-dms-automation-b4bc5d2.tgz` |
|
|
91
|
+
| `@antelopejs/interface-email` | `>=0.0.6 <1.0.0` | `0.0.6` |
|
|
92
|
+
| `@antelopejs/interface-file-storage` | `>=0.1.4 <1.0.0` | `0.1.4` |
|
|
93
|
+
| `@antelopejs/interface-redis` | `>=0.0.9 <1.0.0` | `0.0.9` |
|
|
94
|
+
|
|
95
|
+
<!-- antelopejs-docs-compat:end -->
|
|
96
|
+
|
|
97
|
+
Each DMS release declares the renderer releases it runs on in `engines["@antelopejs/dms-frontend"]` of its frontend module, and the frontend's [compatibility table](https://dms.antelopejs.com/docs/frontend/introduction#compatibility) lists the renderer release line of each DMS release it covers. The DMS also runs embedded in an AdonisJS application: see [Run the DMS inside AdonisJS](https://dms.antelopejs.com/docs/getting-started/adonisjs).
|
|
88
98
|
|
|
89
99
|
## Documentation
|
|
90
100
|
|
package/bin/control/base.mjs
CHANGED
|
@@ -24,8 +24,8 @@ function baseCache() {
|
|
|
24
24
|
* A prepared worktree of the run's base ref, built once per commit under
|
|
25
25
|
* `~/.cache/dms-control/base/<sha>` with the skill's `prepare` commands.
|
|
26
26
|
*
|
|
27
|
-
* @param {
|
|
28
|
-
* @param {
|
|
27
|
+
* @param {import("./run-state.mjs").RunState} state The run
|
|
28
|
+
* @param {import("./verify/skill.mjs").Skill} skill The skill
|
|
29
29
|
* @returns {{ sha: string, dir: string }} The base commit and its worktree
|
|
30
30
|
*/
|
|
31
31
|
function baseCheckout(state, skill) {
|
|
@@ -47,12 +47,17 @@ function baseCheckout(state, skill) {
|
|
|
47
47
|
* containment journey writes data with the base version inside `action`
|
|
48
48
|
* and reads it back with the head version after it.
|
|
49
49
|
*
|
|
50
|
-
* @param {
|
|
51
|
-
* @param {
|
|
50
|
+
* @param {import("./run-state.mjs").RunState} state The run
|
|
51
|
+
* @param {import("./verify/skill.mjs").Skill} skill The skill, whose `prepare` builds the base
|
|
52
52
|
* @param {(sha: string) => Promise<void>} action What to do against the base backend
|
|
53
53
|
* @returns {Promise<string>} The base commit
|
|
54
54
|
*/
|
|
55
55
|
export async function withBaseBackend(state, skill, action) {
|
|
56
|
+
if (state.renderer) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"A run with --renderer has no base backend: the skill's prepare builds the renderer's repository, not the playground's.",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
56
61
|
const base = baseCheckout(state, skill);
|
|
57
62
|
const playground = join(base.dir, relative(state.repo, state.playground));
|
|
58
63
|
state.baseRoot = join(state.dir, "base-root");
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isOutcome } from "./browser-wire.mjs";
|
|
2
|
+
import { decode } from "./json.mjs";
|
|
1
3
|
import { CONTROL_BIN } from "./stack.mjs";
|
|
2
4
|
import { freePort, isAlive, startDetached, waitForOk } from "./processes.mjs";
|
|
3
5
|
import { saveRun } from "./run-state.mjs";
|
|
@@ -5,6 +7,10 @@ import { nextFolder } from "./session.mjs";
|
|
|
5
7
|
|
|
6
8
|
const READY_TIMEOUT_MS = 60_000;
|
|
7
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @param {import("./run-state.mjs").RunState} state The run
|
|
12
|
+
* @returns {Promise<import("./run-state.mjs").ServerRecord>} Its running browser server, started if it was not
|
|
13
|
+
*/
|
|
8
14
|
async function browserServer(state) {
|
|
9
15
|
if (state.browser && isAlive(state.browser.pid)) return state.browser;
|
|
10
16
|
const port = await freePort();
|
|
@@ -28,17 +34,30 @@ async function browserServer(state) {
|
|
|
28
34
|
* page, both kept between commands. The action, the URL, the ARIA snapshot
|
|
29
35
|
* and a screenshot go to `<run>/evidence/browser/NN-<label>/`.
|
|
30
36
|
*
|
|
31
|
-
* @param {
|
|
32
|
-
* @param {
|
|
37
|
+
* @param {import("./run-state.mjs").RunState} state The run
|
|
38
|
+
* @param {Omit<import("./browser.mjs").BrowserRequest, "folder">} request The action: `do`, `account`, `tab`, and the action's target, value, key or path
|
|
33
39
|
* @param {string} label What the step does
|
|
34
40
|
* @returns {Promise<{ ok: boolean, url: string, error: string | null, aria: string, folder: string }>} The outcome
|
|
35
41
|
*/
|
|
36
42
|
export async function browserCommand(state, request, label) {
|
|
37
43
|
const server = await browserServer(state);
|
|
38
44
|
const folder = nextFolder(state, "browser", label);
|
|
39
|
-
const
|
|
45
|
+
const outcome = await postAction(server.port, { ...request, folder });
|
|
46
|
+
return { ...outcome, folder };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {number} port The browser server's port
|
|
51
|
+
* @param {import("./browser.mjs").BrowserRequest} request The action
|
|
52
|
+
* @returns {Promise<import("./browser.mjs").Outcome>} The server's answer
|
|
53
|
+
* @throws When the answer is not an outcome
|
|
54
|
+
*/
|
|
55
|
+
export async function postAction(port, request) {
|
|
56
|
+
const response = await fetch(`http://127.0.0.1:${port}/`, {
|
|
40
57
|
method: "POST",
|
|
41
|
-
body: JSON.stringify(
|
|
58
|
+
body: JSON.stringify(request),
|
|
42
59
|
});
|
|
43
|
-
|
|
60
|
+
/** @type {unknown} */
|
|
61
|
+
const answer = await response.json();
|
|
62
|
+
return decode(isOutcome, answer, "the browser server's answer", "an outcome");
|
|
44
63
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isBoolean,
|
|
3
|
+
isNumber,
|
|
4
|
+
isString,
|
|
5
|
+
nullable,
|
|
6
|
+
objectOf,
|
|
7
|
+
oneOf,
|
|
8
|
+
} from "./json.mjs";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* What the `dms-control browser` client and the browser server post to each
|
|
12
|
+
* other, checked on both sides.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const isRoleName = { role: isString };
|
|
16
|
+
const isRoleOptions = { name: isString, exact: isBoolean };
|
|
17
|
+
|
|
18
|
+
/** @type {import("./json.mjs").Guard<import("./browser.mjs").BrowserRequest>} One browser action, as the client posts it */
|
|
19
|
+
export const isBrowserRequest = objectOf(
|
|
20
|
+
{ do: isString, account: isString, tab: isString, folder: isString },
|
|
21
|
+
{
|
|
22
|
+
target: objectOf(isRoleName, {
|
|
23
|
+
...isRoleOptions,
|
|
24
|
+
nth: isNumber,
|
|
25
|
+
within: objectOf(isRoleName, isRoleOptions),
|
|
26
|
+
}),
|
|
27
|
+
value: isString,
|
|
28
|
+
key: isString,
|
|
29
|
+
path: isString,
|
|
30
|
+
state: oneOf(["attached", "detached", "visible", "hidden"]),
|
|
31
|
+
timeoutMs: isNumber,
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
/** @type {import("./json.mjs").Guard<import("./browser.mjs").Outcome>} What the server answers for one action */
|
|
36
|
+
export const isOutcome = objectOf({
|
|
37
|
+
ok: isBoolean,
|
|
38
|
+
url: isString,
|
|
39
|
+
error: nullable(isString),
|
|
40
|
+
aria: isString,
|
|
41
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import { answerRequest } from "./browser.mjs";
|
|
5
|
+
import { postAction } from "./browser-client.mjs";
|
|
6
|
+
import { isOutcome } from "./browser-wire.mjs";
|
|
7
|
+
import { decode } from "./json.mjs";
|
|
8
|
+
import { freePort } from "./processes.mjs";
|
|
9
|
+
|
|
10
|
+
const REQUEST = {
|
|
11
|
+
do: "click",
|
|
12
|
+
account: "admin",
|
|
13
|
+
tab: "main",
|
|
14
|
+
folder: "/run/evidence/browser/01-click",
|
|
15
|
+
target: { role: "button", name: "Invite", nth: 1 },
|
|
16
|
+
};
|
|
17
|
+
const OUTCOME = { ok: true, url: "/", error: null, aria: "- button" };
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} body What a client posted
|
|
21
|
+
* @returns {Promise<{ answer: import("./browser.mjs").Outcome, handled: unknown[] }>} The answer and the requests that reached the handler
|
|
22
|
+
*/
|
|
23
|
+
async function answered(body) {
|
|
24
|
+
/** @type {unknown[]} */
|
|
25
|
+
const handled = [];
|
|
26
|
+
const answer = await answerRequest(body, (request) => {
|
|
27
|
+
handled.push(request);
|
|
28
|
+
return Promise.resolve(OUTCOME);
|
|
29
|
+
});
|
|
30
|
+
/** @type {unknown} */
|
|
31
|
+
const posted = JSON.parse(answer);
|
|
32
|
+
return {
|
|
33
|
+
answer: decode(isOutcome, posted, "the answer", "an outcome"),
|
|
34
|
+
handled,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await test("a posted request reaches the handler as written and its outcome is the answer", async () => {
|
|
39
|
+
const { answer, handled } = await answered(JSON.stringify(REQUEST));
|
|
40
|
+
assert.deepEqual(handled, [REQUEST]);
|
|
41
|
+
assert.deepEqual(answer, OUTCOME);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await test("a request with a field of the wrong type is answered as a failed action, not run", async () => {
|
|
45
|
+
const { answer, handled } = await answered(
|
|
46
|
+
JSON.stringify({ ...REQUEST, tab: 5 }),
|
|
47
|
+
);
|
|
48
|
+
assert.deepEqual(handled, []);
|
|
49
|
+
assert.equal(answer.ok, false);
|
|
50
|
+
assert.match(
|
|
51
|
+
answer.error ?? "",
|
|
52
|
+
/browser request is malformed: expected a browser action/,
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await test("a request without an action is answered as a failed action, not run", async () => {
|
|
57
|
+
const { answer, handled } = await answered(
|
|
58
|
+
JSON.stringify({ account: "admin" }),
|
|
59
|
+
);
|
|
60
|
+
assert.deepEqual(handled, []);
|
|
61
|
+
assert.match(answer.error ?? "", /malformed: expected a browser action/);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await test("a body that is no JSON is answered as a failed action", async () => {
|
|
65
|
+
const { answer, handled } = await answered("{");
|
|
66
|
+
assert.deepEqual(handled, []);
|
|
67
|
+
assert.equal(answer.ok, false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {import("node:test").TestContext} t The test
|
|
72
|
+
* @param {unknown} answer What the server answers
|
|
73
|
+
* @returns {Promise<number>} The server's port
|
|
74
|
+
*/
|
|
75
|
+
async function serving(t, answer) {
|
|
76
|
+
const port = await freePort();
|
|
77
|
+
const server = createServer((_incoming, outgoing) => {
|
|
78
|
+
outgoing.end(JSON.stringify(answer));
|
|
79
|
+
});
|
|
80
|
+
await new Promise((resolve) => server.listen(port, "127.0.0.1", resolve));
|
|
81
|
+
t.after(() => server.close());
|
|
82
|
+
return port;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await test("the client returns the server's outcome", async (t) => {
|
|
86
|
+
const port = await serving(t, OUTCOME);
|
|
87
|
+
assert.deepEqual(await postAction(port, REQUEST), OUTCOME);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await test("the client refuses an answer that is no outcome", async (t) => {
|
|
91
|
+
const port = await serving(t, { ...OUTCOME, ok: "yes" });
|
|
92
|
+
await assert.rejects(
|
|
93
|
+
postAction(port, REQUEST),
|
|
94
|
+
/the browser server's answer is malformed: expected an outcome/,
|
|
95
|
+
);
|
|
96
|
+
});
|
package/bin/control/browser.mjs
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { writeFileSync } from "node:fs";
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { isBrowserRequest } from "./browser-wire.mjs";
|
|
5
|
+
import { describeFailure } from "./failure.mjs";
|
|
6
|
+
import { decode } from "./json.mjs";
|
|
4
7
|
import { importFrom } from "./mongo.mjs";
|
|
5
8
|
import { loadRun } from "./run-state.mjs";
|
|
6
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import("playwright-core").Browser} Browser
|
|
12
|
+
* @typedef {import("playwright-core").BrowserContext} BrowserContext
|
|
13
|
+
* @typedef {import("playwright-core").Locator} Locator
|
|
14
|
+
* @typedef {import("playwright-core").Page} Page
|
|
15
|
+
* @typedef {{ ok: boolean, url: string, error: string | null, aria: string }} Outcome
|
|
16
|
+
* @typedef {{ do: string, baseUrl: string, target?: Target, value?: string, key?: string, path?: string, state?: "attached" | "detached" | "visible" | "hidden", timeoutMs?: number }} Action
|
|
17
|
+
* @typedef {Omit<Action, "baseUrl"> & { account: string, tab: string, folder: string }} BrowserRequest
|
|
18
|
+
*/
|
|
19
|
+
|
|
7
20
|
const ACTION_TIMEOUT_MS = 15_000;
|
|
8
21
|
const NAVIGATION_TIMEOUT_MS = 60_000;
|
|
9
22
|
const SETTLE_MS = 400;
|
|
@@ -13,9 +26,11 @@ const VIEWPORT = { width: 1280, height: 900 };
|
|
|
13
26
|
* Launches the headless Chromium that playwright-core pins.
|
|
14
27
|
*
|
|
15
28
|
* @param {string[]} from Folders to resolve playwright-core from
|
|
16
|
-
* @returns {Promise<
|
|
29
|
+
* @returns {Promise<Browser>} The Playwright browser
|
|
17
30
|
*/
|
|
18
31
|
export async function launchBrowser(from) {
|
|
32
|
+
/** @type {typeof import("playwright-core")} */
|
|
33
|
+
// oxlint-disable-next-line typescript/no-unsafe-assignment -- importFrom resolves playwright-core by path from the project under test at run time and answers its namespace as any, which the JSDoc type binds to Playwright's own declarations.
|
|
19
34
|
const { chromium } = await importFrom("playwright-core", from);
|
|
20
35
|
return chromium.launch({ headless: true });
|
|
21
36
|
}
|
|
@@ -24,8 +39,8 @@ export async function launchBrowser(from) {
|
|
|
24
39
|
* A new browser context: one per account, so cookies never leak between
|
|
25
40
|
* the users a journey plays.
|
|
26
41
|
*
|
|
27
|
-
* @param {
|
|
28
|
-
* @returns {Promise<
|
|
42
|
+
* @param {Browser} browser The Playwright browser
|
|
43
|
+
* @returns {Promise<BrowserContext>} The context
|
|
29
44
|
*/
|
|
30
45
|
export function newContext(browser) {
|
|
31
46
|
return browser.newContext({
|
|
@@ -39,15 +54,15 @@ export function newContext(browser) {
|
|
|
39
54
|
* What an action targets: an ARIA role and accessible name, optionally
|
|
40
55
|
* inside another one (`within`), or the n-th of several matches.
|
|
41
56
|
*
|
|
42
|
-
* @typedef {{ role: string, name?: string, exact?: boolean, nth?: number, within?: { role: string, name?: string } }} Target
|
|
57
|
+
* @typedef {{ role: string, name?: string | RegExp, exact?: boolean, nth?: number, within?: { role: string, name?: string | RegExp, exact?: boolean } }} Target
|
|
43
58
|
*/
|
|
44
59
|
|
|
45
60
|
/**
|
|
46
61
|
* The locator of a target, by role and accessible name, as a user finds it.
|
|
47
62
|
*
|
|
48
|
-
* @param {
|
|
63
|
+
* @param {Page} page The Playwright page
|
|
49
64
|
* @param {Target} target What to find
|
|
50
|
-
* @returns {
|
|
65
|
+
* @returns {Locator} The locator
|
|
51
66
|
*/
|
|
52
67
|
export function locate(page, target) {
|
|
53
68
|
const scope = target.within
|
|
@@ -76,6 +91,7 @@ export function describeTarget(target) {
|
|
|
76
91
|
return target.within ? `${own} in ${describeTarget(target.within)}` : own;
|
|
77
92
|
}
|
|
78
93
|
|
|
94
|
+
/** @type {Record<string, (page: Page, action: Action) => Promise<unknown> | undefined>} */
|
|
79
95
|
const ACTIONS = {
|
|
80
96
|
goto: (page, action) =>
|
|
81
97
|
page.goto(new URL(action.path, action.baseUrl).href, {
|
|
@@ -123,7 +139,7 @@ export const ACTION_NAMES = Object.keys(ACTIONS);
|
|
|
123
139
|
* Writes what the page shows into `folder`: `url.txt`, `aria.yml` (the ARIA
|
|
124
140
|
* snapshot of the body) and `screenshot.png`.
|
|
125
141
|
*
|
|
126
|
-
* @param {
|
|
142
|
+
* @param {Page} page The Playwright page
|
|
127
143
|
* @param {string} folder The evidence folder
|
|
128
144
|
* @returns {Promise<string>} The ARIA snapshot
|
|
129
145
|
*/
|
|
@@ -132,7 +148,10 @@ export async function capture(page, folder) {
|
|
|
132
148
|
const aria = await page
|
|
133
149
|
.locator("body")
|
|
134
150
|
.ariaSnapshot({ timeout: ACTION_TIMEOUT_MS })
|
|
135
|
-
.catch(
|
|
151
|
+
.catch(
|
|
152
|
+
(/** @type {unknown} */ error) =>
|
|
153
|
+
`snapshot failed: ${describeFailure(error)}`,
|
|
154
|
+
);
|
|
136
155
|
writeFileSync(join(folder, "aria.yml"), `${aria}\n`);
|
|
137
156
|
await page
|
|
138
157
|
.screenshot({
|
|
@@ -148,10 +167,10 @@ export async function capture(page, folder) {
|
|
|
148
167
|
* Runs one action on a page, then captures the page into `folder`. A target
|
|
149
168
|
* that is missing, hidden or disabled past the timeout fails the action.
|
|
150
169
|
*
|
|
151
|
-
* @param {
|
|
152
|
-
* @param {
|
|
170
|
+
* @param {Page} page The Playwright page
|
|
171
|
+
* @param {Action} action What to do
|
|
153
172
|
* @param {string} folder The evidence folder
|
|
154
|
-
* @returns {Promise<
|
|
173
|
+
* @returns {Promise<Outcome>} The outcome
|
|
155
174
|
*/
|
|
156
175
|
export async function perform(page, action, folder) {
|
|
157
176
|
const run = ACTIONS[action.do];
|
|
@@ -171,7 +190,7 @@ export async function perform(page, action, folder) {
|
|
|
171
190
|
.catch(() => undefined);
|
|
172
191
|
await page.waitForTimeout(SETTLE_MS);
|
|
173
192
|
} catch (failure) {
|
|
174
|
-
error =
|
|
193
|
+
error = describeFailure(failure).split("\n")[0];
|
|
175
194
|
}
|
|
176
195
|
const aria = await capture(page, folder);
|
|
177
196
|
return { ok: error === null, url: page.url(), error, aria };
|
|
@@ -180,10 +199,10 @@ export async function perform(page, action, folder) {
|
|
|
180
199
|
/**
|
|
181
200
|
* Signs an account in through the sign-in form, the way a user does.
|
|
182
201
|
*
|
|
183
|
-
* @param {
|
|
202
|
+
* @param {Page} page The Playwright page
|
|
184
203
|
* @param {{ email: string, password: string }} credentials The account
|
|
185
204
|
* @param {{ baseUrl: string, folder: (label: string) => string }} options Where the frontend is and where evidence goes
|
|
186
|
-
* @returns {Promise<
|
|
205
|
+
* @returns {Promise<Outcome>} The outcome of the last step
|
|
187
206
|
*/
|
|
188
207
|
export async function signInThroughForm(page, credentials, options) {
|
|
189
208
|
const steps = [
|
|
@@ -221,6 +240,35 @@ export async function signInThroughForm(page, credentials, options) {
|
|
|
221
240
|
};
|
|
222
241
|
}
|
|
223
242
|
|
|
243
|
+
/**
|
|
244
|
+
* @param {unknown} error What the action threw
|
|
245
|
+
* @returns {Outcome} The failed outcome
|
|
246
|
+
*/
|
|
247
|
+
function failedOutcome(error) {
|
|
248
|
+
return { ok: false, url: "", error: describeFailure(error), aria: "" };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Decodes a posted request and answers it. A body that is no valid request
|
|
253
|
+
* is answered as a failed action, like any action that throws.
|
|
254
|
+
*
|
|
255
|
+
* @param {string} body What the client posted
|
|
256
|
+
* @param {(request: BrowserRequest) => Promise<Outcome>} handle Runs one action
|
|
257
|
+
* @returns {Promise<string>} The outcome as JSON
|
|
258
|
+
*/
|
|
259
|
+
export function answerRequest(body, handle) {
|
|
260
|
+
return Promise.resolve()
|
|
261
|
+
.then(() => {
|
|
262
|
+
/** @type {unknown} */
|
|
263
|
+
const posted = JSON.parse(body);
|
|
264
|
+
return handle(
|
|
265
|
+
decode(isBrowserRequest, posted, "browser request", "a browser action"),
|
|
266
|
+
);
|
|
267
|
+
})
|
|
268
|
+
.catch(failedOutcome)
|
|
269
|
+
.then((outcome) => JSON.stringify(outcome));
|
|
270
|
+
}
|
|
271
|
+
|
|
224
272
|
/**
|
|
225
273
|
* Serves the run's browser for `dms-control browser`: one context per
|
|
226
274
|
* account and one page per tab name, kept between commands, driven by JSON
|
|
@@ -235,8 +283,15 @@ export async function signInThroughForm(page, credentials, options) {
|
|
|
235
283
|
export async function serveBrowser(dir, from, port) {
|
|
236
284
|
const browser = await launchBrowser(from);
|
|
237
285
|
browser.on("disconnected", () => process.exit(0));
|
|
286
|
+
/** @type {Map<string, BrowserContext>} */
|
|
238
287
|
const contexts = new Map();
|
|
288
|
+
/** @type {Map<string, Page>} */
|
|
239
289
|
const pages = new Map();
|
|
290
|
+
/**
|
|
291
|
+
* @param {string} account The account
|
|
292
|
+
* @param {string} tab The tab name
|
|
293
|
+
* @returns {Promise<Page>} The tab's page
|
|
294
|
+
*/
|
|
240
295
|
const pageFor = async (account, tab) => {
|
|
241
296
|
const key = `${account}/${tab}`;
|
|
242
297
|
if (!pages.has(key)) {
|
|
@@ -246,6 +301,10 @@ export async function serveBrowser(dir, from, port) {
|
|
|
246
301
|
}
|
|
247
302
|
return pages.get(key);
|
|
248
303
|
};
|
|
304
|
+
/**
|
|
305
|
+
* @param {BrowserRequest} request What the client posted
|
|
306
|
+
* @returns {Promise<Outcome>} The outcome
|
|
307
|
+
*/
|
|
249
308
|
const handle = async (request) => {
|
|
250
309
|
const state = loadRun(dir);
|
|
251
310
|
const page = await pageFor(request.account, request.tab);
|
|
@@ -270,21 +329,13 @@ export async function serveBrowser(dir, from, port) {
|
|
|
270
329
|
outgoing.end("ok");
|
|
271
330
|
return;
|
|
272
331
|
}
|
|
332
|
+
/** @type {Buffer[]} */
|
|
273
333
|
const chunks = [];
|
|
274
|
-
incoming.on("data", (chunk) => chunks.push(chunk));
|
|
334
|
+
incoming.on("data", (/** @type {Buffer} */ chunk) => chunks.push(chunk));
|
|
275
335
|
incoming.on("end", () => {
|
|
276
|
-
|
|
277
|
-
.then((
|
|
278
|
-
.catch((
|
|
279
|
-
outgoing.end(
|
|
280
|
-
JSON.stringify({
|
|
281
|
-
ok: false,
|
|
282
|
-
url: "",
|
|
283
|
-
error: String(error?.message ?? error),
|
|
284
|
-
aria: "",
|
|
285
|
-
}),
|
|
286
|
-
),
|
|
287
|
-
);
|
|
336
|
+
answerRequest(Buffer.concat(chunks).toString("utf8"), handle)
|
|
337
|
+
.then((answer) => outgoing.end(answer))
|
|
338
|
+
.catch(() => outgoing.destroy());
|
|
288
339
|
});
|
|
289
340
|
});
|
|
290
341
|
await new Promise((resolve) => server.listen(port, "127.0.0.1", resolve));
|