@protobase/ui 0.33.4 → 0.34.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.
Files changed (92) hide show
  1. package/dist/account/account-security-page.js +16 -6
  2. package/dist/account/connected-apps-section.js +93 -0
  3. package/dist/app/app.js +20 -7
  4. package/dist/app/charts/chart-view.js +1 -1
  5. package/dist/app/create/create-page.js +2 -2
  6. package/dist/app/create/create-route.js +1 -1
  7. package/dist/app/delete/use-delete-flow.js +1 -1
  8. package/dist/app/displays/board-display.js +1 -1
  9. package/dist/app/displays/calendar-display.js +1 -1
  10. package/dist/app/displays/map-display.js +1 -1
  11. package/dist/app/displays/record-card.js +1 -1
  12. package/dist/app/displays/timeline-display.js +1 -1
  13. package/dist/app/displays/use-board-column.js +1 -1
  14. package/dist/app/displays/use-map-records.js +1 -1
  15. package/dist/app/embedded-list/embedded-list.js +1 -1
  16. package/dist/app/embedded-list/use-embedded-rows.js +1 -1
  17. package/dist/app/export/export-dialog.js +1 -1
  18. package/dist/app/file-field.js +1 -1
  19. package/dist/app/gallery/gallery-section.js +1 -1
  20. package/dist/app/gallery/lightbox.js +1 -1
  21. package/dist/app/gallery/use-gallery-photos.js +1 -1
  22. package/dist/app/hand-order/hand-sorted-list.js +1 -1
  23. package/dist/app/hand-order/use-hand-order.js +1 -1
  24. package/dist/app/import/import-dialog.js +1 -1
  25. package/dist/app/incoming-card.js +1 -1
  26. package/dist/app/list-page.js +3 -3
  27. package/dist/app/map/map-section.js +1 -1
  28. package/dist/app/meta-gate.js +2 -2
  29. package/dist/app/pages/action-inputs/action-inputs-dialog.js +2 -2
  30. package/dist/app/pages/blocks/card-row.js +1 -1
  31. package/dist/app/pages/blocks/field.js +1 -1
  32. package/dist/app/pages/blocks/modal-form.js +1 -1
  33. package/dist/app/pages/blocks/record-card.js +1 -1
  34. package/dist/app/pages/blocks/record-cards.js +1 -1
  35. package/dist/app/pages/blocks/stat.js +1 -1
  36. package/dist/app/pages/composed-page.js +1 -1
  37. package/dist/app/pages/field-value.js +1 -1
  38. package/dist/app/pages/use-run-action.js +1 -1
  39. package/dist/app/record-chart/record-chart-section.js +1 -1
  40. package/dist/app/record-lines.js +1 -1
  41. package/dist/app/record-page.js +5 -5
  42. package/dist/app/record-row/record-row-body.js +1 -1
  43. package/dist/app/record-row/saved-record-row.js +1 -1
  44. package/dist/app/related-cards/related-card.js +1 -1
  45. package/dist/app/related-section.js +2 -2
  46. package/dist/app/saved/use-favorite.js +1 -1
  47. package/dist/app/search/navigation-hits.js +1 -1
  48. package/dist/app/search/use-global-search.js +1 -1
  49. package/dist/app/searches/save-search-button.js +1 -1
  50. package/dist/app/searches/saved-search-rows.js +1 -1
  51. package/dist/app/searches/saved-searches.js +1 -1
  52. package/dist/app/searches/search-controls.js +1 -1
  53. package/dist/app/searches/update-search-dialog.js +1 -1
  54. package/dist/app/searches/use-save-search.js +1 -1
  55. package/dist/app/searches/use-saved-searches.js +1 -1
  56. package/dist/app/source-notice.js +1 -1
  57. package/dist/app/use-app-address.js +1 -1
  58. package/dist/app/use-incoming-counts.js +1 -1
  59. package/dist/auth/auth-gate.js +1 -1
  60. package/dist/auth/auth-provider.js +4 -0
  61. package/dist/auth/sign-in-prompt.js +2 -2
  62. package/dist/connect/client-name.js +9 -0
  63. package/dist/connect/client-publisher.js +13 -0
  64. package/dist/connect/connect-page.js +236 -0
  65. package/dist/connect/interaction-guard.js +77 -0
  66. package/dist/connect/redirect-target.js +36 -0
  67. package/dist/connect/signed-request.js +14 -0
  68. package/dist/connect/use-connect-address.js +11 -0
  69. package/dist/data/use-live-updates.js +1 -1
  70. package/dist/forms/public-form-page.js +2 -2
  71. package/dist/index.js +3 -3
  72. package/dist/live/use-filter-config.js +1 -1
  73. package/dist/live/use-reference-search.js +1 -1
  74. package/dist/package.js +1 -1
  75. package/dist/plugins/plugin-gate.js +1 -1
  76. package/dist/shell/assets/app--vnZRzES.js +70 -0
  77. package/dist/shell/assets/index-BX850JXd.css +2 -0
  78. package/dist/shell/assets/{index-BAYZFH-l.js → index-DUcwNz5T.js} +2 -2
  79. package/dist/shell/assets/{records-map-D2XP8QkT.js → records-map-4gB4TBHO.js} +2 -2
  80. package/dist/shell/assets/shared-protobase-ui-Bj2wjoBo.js +2 -0
  81. package/dist/shell/index.html +4 -4
  82. package/dist/shell/protobase.shell.json +1 -1
  83. package/dist/shell/protobase.sw.js +2 -2
  84. package/dist/shell-source-maps/assets/app--vnZRzES.js.map +1 -0
  85. package/dist/shell-source-maps/assets/{index-BAYZFH-l.js.map → index-DUcwNz5T.js.map} +1 -1
  86. package/dist/shell-source-maps/assets/{records-map-D2XP8QkT.js.map → records-map-4gB4TBHO.js.map} +1 -1
  87. package/dist/shell-source-maps/assets/{shared-protobase-ui-C5AR2k3j.js.map → shared-protobase-ui-Bj2wjoBo.js.map} +1 -1
  88. package/package.json +4 -4
  89. package/dist/shell/assets/app-B9bnfmK5.js +0 -70
  90. package/dist/shell/assets/index-BrgzNBVN.css +0 -2
  91. package/dist/shell/assets/shared-protobase-ui-C5AR2k3j.js +0 -2
  92. package/dist/shell-source-maps/assets/app-B9bnfmK5.js.map +0 -1
@@ -1,11 +1,11 @@
1
1
  import { cn } from "../../lib/cn.js";
2
2
  import { keys } from "../../data/query-keys.js";
3
3
  import { useClient } from "../../data/api-provider.js";
4
+ import { useAdminMeta } from "../meta-gate.js";
4
5
  import { fileShown, shownImage } from "../../live/file-values.js";
5
6
  import { isMarkdown } from "../../markdown/markdown-hint.js";
6
7
  import { contactFormat } from "../../live/contact-links.js";
7
8
  import { displayValue, valueTitle } from "../../live/display-value.js";
8
- import { useAdminMeta } from "../meta-gate.js";
9
9
  import { AppLink } from "../use-app-address.js";
10
10
  import { FileImage } from "../../live/file-image.js";
11
11
  import { useShownWidget } from "../field-types/shown-widget.js";
@@ -1,7 +1,7 @@
1
1
  import { useClient, useResourceClient } from "../../data/api-provider.js";
2
+ import { useAdminMeta, usePermissions } from "../meta-gate.js";
2
3
  import { text } from "./messages.js";
3
4
  import { useToast } from "../../toasts/toast-provider.js";
4
- import { useAdminMeta, usePermissions } from "../meta-gate.js";
5
5
  import { useAppAddress } from "../use-app-address.js";
6
6
  import { useProjectUi } from "./project-ui.js";
7
7
  import { can } from "../permissions.js";
@@ -1,9 +1,9 @@
1
1
  import { Skeleton } from "../../primitives/skeleton.js";
2
2
  import { Segmented } from "../../primitives/segmented.js";
3
3
  import { viewerZone } from "../../format/zone.js";
4
+ import { useAdminMeta } from "../meta-gate.js";
4
5
  import { humanize } from "../../live/naming.js";
5
6
  import { Section } from "../../record-view/section.js";
6
- import { useAdminMeta } from "../meta-gate.js";
7
7
  import { useAggregates } from "../../data/use-aggregates.js";
8
8
  import { PeriodChart } from "../charts/period-chart.js";
9
9
  import { chartPoints, rangeBuckets } from "./chart-points.js";
@@ -1,6 +1,6 @@
1
+ import { usePermissions } from "./meta-gate.js";
1
2
  import { InlineChildTable } from "../inline-child-table/inline-child-table.js";
2
3
  import { Section } from "../record-view/section.js";
3
- import { usePermissions } from "./meta-gate.js";
4
4
  import { can } from "./permissions.js";
5
5
  import { text } from "./record-messages.js";
6
6
  import { jsx } from "react/jsx-runtime";
@@ -3,11 +3,13 @@ import { Skeleton } from "../primitives/skeleton.js";
3
3
  import { inApp } from "../app-shell/breadcrumb.js";
4
4
  import { useAuth } from "../auth/auth-provider.js";
5
5
  import { Button, buttonClass } from "../primitives/button.js";
6
+ import { namedIcon } from "./named-icon.js";
7
+ import { text } from "./messages.js";
8
+ import { useNavigationGuard } from "./router.js";
6
9
  import { usePauseFocusRefresh } from "../data/focus-refresh.js";
7
10
  import { keys } from "../data/query-keys.js";
8
11
  import { useClient } from "../data/api-provider.js";
9
- import { text } from "./messages.js";
10
- import { useNavigationGuard } from "./router.js";
12
+ import { Notice, useAdminMeta, usePermissions } from "./meta-gate.js";
11
13
  import { useOpenedRecord } from "../data/use-record.js";
12
14
  import { humanize } from "../live/naming.js";
13
15
  import { Field } from "../record-view/field.js";
@@ -21,7 +23,6 @@ import { contactFormat } from "../live/contact-links.js";
21
23
  import { toneFor } from "../live/columns-from-model.js";
22
24
  import { displayValue, valueTitle } from "../live/display-value.js";
23
25
  import { filledFields } from "./filled-fields.js";
24
- import { Notice, useAdminMeta, usePermissions } from "./meta-gate.js";
25
26
  import { AppLink, useAppAddress } from "./use-app-address.js";
26
27
  import { GroupSection } from "../record-view/group-section.js";
27
28
  import { RecordTabs } from "../record-view/record-tabs.js";
@@ -41,7 +42,6 @@ import { useFocusFirstInvalid } from "./use-focus-first-invalid.js";
41
42
  import { can, fieldAccess } from "./permissions.js";
42
43
  import { ValueBadge } from "./value-badge.js";
43
44
  import { FieldValue } from "./pages/field-value.js";
44
- import { namedIcon } from "./named-icon.js";
45
45
  import { text as text$1 } from "./record-messages.js";
46
46
  import { useDeleteFlow } from "./delete/use-delete-flow.js";
47
47
  import { useRestore } from "./delete/use-restore.js";
@@ -69,8 +69,8 @@ import { Fragment, useEffect, useMemo } from "react";
69
69
  import { Check } from "lucide-react";
70
70
  import { isApiError } from "@protobase/client";
71
71
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
72
- import { useQueryClient } from "@tanstack/react-query";
73
72
  import { enumLabel } from "@protobase/schema";
73
+ import { useQueryClient } from "@tanstack/react-query";
74
74
  //#region src/app/record-page.tsx
75
75
  const hidden = (model, name) => name === model.tenant || model.primaryKey.includes(name);
76
76
  /**
@@ -1,10 +1,10 @@
1
1
  import { formatTag } from "../../locale/active-locale.js";
2
2
  import { useIsXs } from "../../lib/use-media-query.js";
3
3
  import { Skeleton } from "../../primitives/skeleton.js";
4
+ import { useAdminMeta } from "../meta-gate.js";
4
5
  import { humanize } from "../../live/naming.js";
5
6
  import { displaysOf, loadedFields } from "../displays/list-displays.js";
6
7
  import { defaultOrderBy } from "../list-order.js";
7
- import { useAdminMeta } from "../meta-gate.js";
8
8
  import { AppLink } from "../use-app-address.js";
9
9
  import { ErrorBanner } from "../error-banner.js";
10
10
  import { viewAllAddress } from "../embedded-list/embedded-display.js";
@@ -1,7 +1,7 @@
1
1
  import { Skeleton } from "../../primitives/skeleton.js";
2
+ import { useAdminMeta } from "../meta-gate.js";
2
3
  import { humanize } from "../../live/naming.js";
3
4
  import { listedResource } from "../current-address.js";
4
- import { useAdminMeta } from "../meta-gate.js";
5
5
  import { ErrorBanner } from "../error-banner.js";
6
6
  import { savedScope } from "../saved/saved-records.js";
7
7
  import { useSavedRecords } from "../saved/use-saved-records.js";
@@ -1,6 +1,6 @@
1
1
  import { Skeleton } from "../../primitives/skeleton.js";
2
- import { recordKey, recordTitle } from "../../live/model-helpers.js";
3
2
  import { useAdminMeta } from "../meta-gate.js";
3
+ import { recordKey, recordTitle } from "../../live/model-helpers.js";
4
4
  import { AppLink } from "../use-app-address.js";
5
5
  import { RecordScope } from "../pages/record-scope.js";
6
6
  import { ActionButton } from "../pages/blocks/action.js";
@@ -1,9 +1,9 @@
1
1
  import { Skeleton } from "../primitives/skeleton.js";
2
2
  import { keys } from "../data/query-keys.js";
3
3
  import { useClient } from "../data/api-provider.js";
4
+ import { useAdminMeta, usePermissions } from "./meta-gate.js";
4
5
  import { Section } from "../record-view/section.js";
5
6
  import { listColumnNames } from "../live/columns-from-model.js";
6
- import { useAdminMeta, usePermissions } from "./meta-gate.js";
7
7
  import { ErrorBanner } from "./error-banner.js";
8
8
  import { countQuery } from "./pages/use-layout-data.js";
9
9
  import { RelatedCards } from "./related-cards/related-cards.js";
@@ -17,8 +17,8 @@ import { HandSortedList } from "./hand-order/hand-sorted-list.js";
17
17
  import { ReorderButton } from "./hand-order/reorder-button.js";
18
18
  import { useState } from "react";
19
19
  import { jsx, jsxs } from "react/jsx-runtime";
20
- import { useQuery } from "@tanstack/react-query";
21
20
  import { createWhere, printFilter } from "@protobase/schema";
21
+ import { useQuery } from "@tanstack/react-query";
22
22
  //#region src/app/related-section.tsx
23
23
  const where$1 = createWhere();
24
24
  const and = (...parts) => {
@@ -1,6 +1,6 @@
1
1
  import { useClient } from "../../data/api-provider.js";
2
- import { useToast } from "../../toasts/toast-provider.js";
3
2
  import { useAdminMeta } from "../meta-gate.js";
3
+ import { useToast } from "../../toasts/toast-provider.js";
4
4
  import { can } from "../permissions.js";
5
5
  import { useRefreshResource } from "../pages/use-layout-data.js";
6
6
  import { savedFor, withSaved, withoutSaved } from "./saved-records.js";
@@ -1,5 +1,5 @@
1
- import { humanize } from "../../live/naming.js";
2
1
  import { allowEverything } from "../../data/meta-data.js";
2
+ import { humanize } from "../../live/naming.js";
3
3
  import { text } from "../create/messages.js";
4
4
  import { createOffered } from "../create/create-offered.js";
5
5
  import { navFromMeta } from "../nav-from-meta.js";
@@ -1,5 +1,5 @@
1
- import { useClient } from "../../data/api-provider.js";
2
1
  import { useRouter } from "../router.js";
2
+ import { useClient } from "../../data/api-provider.js";
3
3
  import { useAdminMeta } from "../meta-gate.js";
4
4
  import { useDebounced } from "../../lib/use-debounced.js";
5
5
  import { navigationHits } from "./navigation-hits.js";
@@ -1,6 +1,6 @@
1
1
  import { Button } from "../../primitives/button.js";
2
- import { text } from "./messages.js";
3
2
  import { SignInPrompt } from "../../auth/sign-in-prompt.js";
3
+ import { text } from "./messages.js";
4
4
  import { SearchNameDialog } from "./search-name-dialog.js";
5
5
  import { UpdateSearchDialog } from "./update-search-dialog.js";
6
6
  import { useSaveSearch } from "./use-save-search.js";
@@ -1,7 +1,7 @@
1
1
  import { Skeleton } from "../../primitives/skeleton.js";
2
+ import { useAdminMeta } from "../meta-gate.js";
2
3
  import { humanize } from "../../live/naming.js";
3
4
  import { recordKey } from "../../live/model-helpers.js";
4
- import { useAdminMeta } from "../meta-gate.js";
5
5
  import { AppLink } from "../use-app-address.js";
6
6
  import { ErrorBanner } from "../error-banner.js";
7
7
  import { text } from "./messages.js";
@@ -1,9 +1,9 @@
1
1
  import { Skeleton } from "../../primitives/skeleton.js";
2
2
  import { PageHeader } from "../../app-shell/page-header.js";
3
+ import { Notice, useAdminMeta, usePermissions } from "../meta-gate.js";
3
4
  import { slotted } from "../../locale/slotted.js";
4
5
  import { humanize } from "../../live/naming.js";
5
6
  import { recordKey } from "../../live/model-helpers.js";
6
- import { Notice, useAdminMeta, usePermissions } from "../meta-gate.js";
7
7
  import { AppLink } from "../use-app-address.js";
8
8
  import { ErrorBanner } from "../error-banner.js";
9
9
  import { can } from "../permissions.js";
@@ -6,8 +6,8 @@ import { useRefreshResource } from "../pages/use-layout-data.js";
6
6
  import { text } from "./messages.js";
7
7
  import { useId } from "react";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
- import { useMutation } from "@tanstack/react-query";
10
9
  import { enumLabel } from "@protobase/schema";
10
+ import { useMutation } from "@tanstack/react-query";
11
11
  //#region src/app/searches/search-controls.tsx
12
12
  const message = (error) => error instanceof Error ? error.message : text("somethingWrong");
13
13
  const Control = ({ model, view, name, value, onChange }) => {
@@ -1,9 +1,9 @@
1
1
  import { Skeleton } from "../../primitives/skeleton.js";
2
2
  import { Button } from "../../primitives/button.js";
3
+ import { ResponsiveDialog } from "../../primitives/responsive-dialog.js";
3
4
  import { useClient } from "../../data/api-provider.js";
4
5
  import { humanize } from "../../live/naming.js";
5
6
  import { defaultOrderBy } from "../list-order.js";
6
- import { ResponsiveDialog } from "../../primitives/responsive-dialog.js";
7
7
  import { countQuery } from "../pages/use-layout-data.js";
8
8
  import { text } from "./messages.js";
9
9
  import { useFilterChips } from "./use-filter-chips.js";
@@ -1,8 +1,8 @@
1
1
  import { useClient } from "../../data/api-provider.js";
2
+ import { useAdminMeta } from "../meta-gate.js";
2
3
  import { humanize } from "../../live/naming.js";
3
4
  import { useToast } from "../../toasts/toast-provider.js";
4
5
  import { recordKey } from "../../live/model-helpers.js";
5
- import { useAdminMeta } from "../meta-gate.js";
6
6
  import { useAppAddress } from "../use-app-address.js";
7
7
  import { can } from "../permissions.js";
8
8
  import { useRefreshResource } from "../pages/use-layout-data.js";
@@ -1,7 +1,7 @@
1
1
  import { keys } from "../../data/query-keys.js";
2
2
  import { useClient } from "../../data/api-provider.js";
3
- import { defaultOrderBy } from "../list-order.js";
4
3
  import { useAdminMeta } from "../meta-gate.js";
4
+ import { defaultOrderBy } from "../list-order.js";
5
5
  import { useQuery } from "@tanstack/react-query";
6
6
  //#region src/app/searches/use-saved-searches.ts
7
7
  /** Where the user's saved searches of `resource` are cached; a list key, so a write to the resource refreshes it. */
@@ -1,7 +1,7 @@
1
1
  import { Spinner } from "../primitives/spinner.js";
2
2
  import { Button } from "../primitives/button.js";
3
- import { keys } from "../data/query-keys.js";
4
3
  import { text } from "./messages.js";
4
+ import { keys } from "../data/query-keys.js";
5
5
  import { isApiError } from "@protobase/client";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import { useQueryClient } from "@tanstack/react-query";
@@ -1,7 +1,7 @@
1
1
  import { useIsXs } from "../lib/use-media-query.js";
2
2
  import { Link, useRouter } from "./router.js";
3
- import { changedList, currentAddress, listDefaults, listedResource } from "./current-address.js";
4
3
  import { useAdminMeta } from "./meta-gate.js";
4
+ import { changedList, currentAddress, listDefaults, listedResource } from "./current-address.js";
5
5
  import { useCallback, useMemo } from "react";
6
6
  import { jsx } from "react/jsx-runtime";
7
7
  import { appPath, readAppPath } from "@protobase/schema";
@@ -1,7 +1,7 @@
1
1
  import { keys } from "../data/query-keys.js";
2
2
  import { useClient } from "../data/api-provider.js";
3
- import { useQueries } from "@tanstack/react-query";
4
3
  import { createWhere, printFilter } from "@protobase/schema";
4
+ import { useQueries } from "@tanstack/react-query";
5
5
  //#region src/app/use-incoming-counts.ts
6
6
  const where$1 = createWhere();
7
7
  /** How many records point at `key` through each relation: `undefined` while loading, left out when the list is refused. */
@@ -5,6 +5,7 @@ import { text } from "./messages.js";
5
5
  import { useAuth } from "./auth-provider.js";
6
6
  import { Button } from "../primitives/button.js";
7
7
  import { useRouter } from "../app/router.js";
8
+ import { useSignInAddress } from "./use-sign-in-address.js";
8
9
  import { EmailCodePage } from "./email-code-page.js";
9
10
  import { FirstRunPage } from "./first-run-page.js";
10
11
  import { readResetLink, withoutResetLink } from "./reset-link.js";
@@ -17,7 +18,6 @@ import { SignUpPage } from "./sign-up-page.js";
17
18
  import { readStaffLink, withoutStaffLink } from "./staff-link.js";
18
19
  import { StaffSignInPage } from "./staff-sign-in-page.js";
19
20
  import { TwoFactorPage } from "./two-factor-page.js";
20
- import { useSignInAddress } from "./use-sign-in-address.js";
21
21
  import { readInvitationLink, withoutInvitationLink } from "../organizations/invitation-link.js";
22
22
  import { InvitationPage } from "../organizations/invitation-page.js";
23
23
  import { BeforeSignIn } from "../plugins/before-sign-in.js";
@@ -18,6 +18,7 @@ const AuthProvider = ({ session: given, origin, children }) => {
18
18
  const [state, setState] = useState({ kind: "loading" });
19
19
  const [signInMethods, setSignInMethods] = useState(["password"]);
20
20
  const [passwordReset, setPasswordReset] = useState(false);
21
+ const [mcp, setMcp] = useState(false);
21
22
  const [canSignUp, setCanSignUp] = useState(false);
22
23
  const [passkeyDomains, setPasskeyDomains] = useState([]);
23
24
  const [socialProviders, setSocialProviders] = useState([]);
@@ -57,6 +58,7 @@ const AuthProvider = ({ session: given, origin, children }) => {
57
58
  session.status().then(async (status) => {
58
59
  setSignInMethods(status.signInMethods);
59
60
  setPasswordReset(status.passwordReset);
61
+ setMcp(Boolean(status.mcp));
60
62
  setCanSignUp(Boolean(status.signUp));
61
63
  setPasskeyDomains(status.passkeyDomains ?? []);
62
64
  setSocialProviders(status.socialProviders);
@@ -81,6 +83,7 @@ const AuthProvider = ({ session: given, origin, children }) => {
81
83
  session,
82
84
  signInMethods,
83
85
  passwordReset,
86
+ mcp,
84
87
  canSignUp,
85
88
  passkeyDomains,
86
89
  socialProviders,
@@ -132,6 +135,7 @@ const AuthProvider = ({ session: given, origin, children }) => {
132
135
  session,
133
136
  signInMethods,
134
137
  passwordReset,
138
+ mcp,
135
139
  canSignUp,
136
140
  passkeyDomains,
137
141
  socialProviders,
@@ -2,9 +2,9 @@ import { RouteLink } from "../primitives/route-link.js";
2
2
  import { text } from "./messages.js";
3
3
  import { useAuth } from "./auth-provider.js";
4
4
  import { Button, buttonClass } from "../primitives/button.js";
5
- import { useSignInAddress } from "./use-sign-in-address.js";
6
- import { ResponsiveDialog } from "../primitives/responsive-dialog.js";
7
5
  import { namedIcon } from "../app/named-icon.js";
6
+ import { ResponsiveDialog } from "../primitives/responsive-dialog.js";
7
+ import { useSignInAddress } from "./use-sign-in-address.js";
8
8
  import { useId } from "react";
9
9
  import { Check, LogIn } from "lucide-react";
10
10
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -0,0 +1,9 @@
1
+ const hidden = /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u115f\u1160\u180e\u200b-\u200f\u2028-\u202e\u2060-\u206f\u3164\ufe00-\ufe0f\ufeff]/g;
2
+ /** A client's own name as one line of plain text, without characters that hide or turn text, cut to `clientNameLength`. */
3
+ const clientName = (name) => {
4
+ const plain = (name ?? "").replace(hidden, " ").replace(/\s+/g, " ").trim();
5
+ if (!plain) return void 0;
6
+ return plain.length > 60 ? `${plain.slice(0, 59)}…` : plain;
7
+ };
8
+ //#endregion
9
+ export { clientName };
@@ -0,0 +1,13 @@
1
+ //#region src/connect/client-publisher.ts
2
+ /**
3
+ * Who published a client: the host of its id when the id is an https URL, a client metadata document the server
4
+ * fetched from there, the one identity of a client the server checked. `undefined` for a client that registered
5
+ * itself, whose id the server chose, and whose name says nothing about who made it.
6
+ */
7
+ const clientPublisher = (clientId) => {
8
+ if (!URL.canParse(clientId)) return void 0;
9
+ const url = new URL(clientId);
10
+ return url.protocol === "https:" ? url.host : void 0;
11
+ };
12
+ //#endregion
13
+ export { clientPublisher };
@@ -0,0 +1,236 @@
1
+ import { Spinner } from "../primitives/spinner.js";
2
+ import { authMessage } from "../auth/auth-messages.js";
3
+ import { useAuth } from "../auth/auth-provider.js";
4
+ import { clientName } from "./client-name.js";
5
+ import { clientPublisher } from "./client-publisher.js";
6
+ import { Button } from "../primitives/button.js";
7
+ import { useRouter } from "../app/router.js";
8
+ import { Notice } from "../app/meta-gate.js";
9
+ import { isFramed, useArmedAfterFocus, useGuardedPress } from "./interaction-guard.js";
10
+ import { isSafeTarget, redirectTarget } from "./redirect-target.js";
11
+ import { signedRequestFor } from "./signed-request.js";
12
+ import { useConnectAddress } from "./use-connect-address.js";
13
+ import { useEffect, useState } from "react";
14
+ import { AuthError } from "@protobase/client";
15
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
16
+ //#region src/connect/connect-page.tsx
17
+ const refusals = {
18
+ invalid_signature: "This request has expired: connect again from your app.",
19
+ STAFF_CANNOT_CONNECT: "Staff signed in as someone cannot allow apps to act for them.",
20
+ SIGN_IN_SETUP_REQUIRED: "Set up what this app requires for signing in first, then connect again from your app.",
21
+ ADDRESS_NOT_VERIFIED: "Confirm your address first: sign out, then sign in with a code emailed to you, and connect again from your app."
22
+ };
23
+ const refusalOf = (failure) => {
24
+ if (!(failure instanceof AuthError)) throw failure;
25
+ return (failure.code && refusals[failure.code]) ?? authMessage(failure);
26
+ };
27
+ const goWith = (assign) => (url) => {
28
+ if (!isSafeTarget(url)) throw new AuthError("The app asked to be sent somewhere this page will not go.", 400);
29
+ assign(url);
30
+ };
31
+ const TargetText = ({ target }) => target.kind === "site" ? /* @__PURE__ */ jsxs(Fragment$1, { children: ["the website ", /* @__PURE__ */ jsx("strong", {
32
+ className: "font-medium text-foreground",
33
+ children: target.host
34
+ })] }) : target.kind === "this-computer" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
35
+ "an app on this computer (port ",
36
+ target.port,
37
+ ")"
38
+ ] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: ["an app on this device that opens ", /* @__PURE__ */ jsx("code", {
39
+ className: "break-all text-foreground",
40
+ children: target.uri
41
+ })] });
42
+ const Request = ({ asking, appName, email, onAnswer, onSignOut }) => {
43
+ const [busy, setBusy] = useState(false);
44
+ const armed = useArmedAfterFocus(500);
45
+ const answer = (accept) => {
46
+ setBusy(true);
47
+ onAnswer(accept).finally(() => setBusy(false));
48
+ };
49
+ const allow = useGuardedPress(armed && !busy, () => answer(true));
50
+ const name = clientName(asking.client.client_name);
51
+ const publisher = clientPublisher(asking.client.client_id);
52
+ const { target } = asking;
53
+ return /* @__PURE__ */ jsx("div", {
54
+ className: "mx-auto flex w-full max-w-lg flex-col gap-4 px-4 py-10",
55
+ children: /* @__PURE__ */ jsxs("section", {
56
+ "aria-label": "Allow an app",
57
+ className: "rounded-lg border bg-background p-5",
58
+ children: [
59
+ /* @__PURE__ */ jsxs("h1", {
60
+ className: "text-[16px] font-semibold",
61
+ children: [
62
+ "Allow an app to act as you in ",
63
+ appName,
64
+ "?"
65
+ ]
66
+ }),
67
+ /* @__PURE__ */ jsxs("p", {
68
+ className: "mt-3",
69
+ children: [
70
+ "An app that calls itself",
71
+ " ",
72
+ /* @__PURE__ */ jsxs("bdi", {
73
+ className: "inline-block max-w-full truncate align-bottom font-medium",
74
+ title: name,
75
+ children: [
76
+ "“",
77
+ name ?? "no name",
78
+ "”"
79
+ ]
80
+ }),
81
+ " ",
82
+ "asks to act as you here."
83
+ ]
84
+ }),
85
+ publisher && /* @__PURE__ */ jsxs("p", {
86
+ className: "mt-1 text-muted-foreground",
87
+ children: [
88
+ "Published by ",
89
+ publisher,
90
+ "."
91
+ ]
92
+ }),
93
+ target ? /* @__PURE__ */ jsxs("p", {
94
+ className: "mt-3 text-muted-foreground",
95
+ children: [
96
+ "Allowing sends you on to ",
97
+ /* @__PURE__ */ jsx(TargetText, { target }),
98
+ "."
99
+ ]
100
+ }) : /* @__PURE__ */ jsx("p", {
101
+ role: "alert",
102
+ className: "mt-3 text-danger-text",
103
+ children: "This app asked to be sent somewhere this page will not go, so it cannot be allowed."
104
+ }),
105
+ /* @__PURE__ */ jsx("p", {
106
+ className: "mt-3 text-muted-foreground",
107
+ children: "It can then read and change what you can, with your current role and organization, until you disconnect it from your account page."
108
+ }),
109
+ /* @__PURE__ */ jsxs("div", {
110
+ className: "mt-5 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
111
+ children: [/* @__PURE__ */ jsx(Button, {
112
+ disabled: busy,
113
+ onClick: () => answer(false),
114
+ children: "Deny"
115
+ }), target && /* @__PURE__ */ jsx(Button, {
116
+ variant: "primary",
117
+ loading: busy,
118
+ disabled: !armed || busy,
119
+ ...allow,
120
+ children: "Allow"
121
+ })]
122
+ }),
123
+ /* @__PURE__ */ jsxs("p", {
124
+ className: "mt-4 border-t pt-3 text-xs text-muted-foreground",
125
+ children: [
126
+ "Signed in as ",
127
+ email,
128
+ ".",
129
+ " ",
130
+ /* @__PURE__ */ jsx("button", {
131
+ type: "button",
132
+ className: "underline underline-offset-2 hover:text-foreground",
133
+ onClick: onSignOut,
134
+ children: "Not you? Sign out"
135
+ })
136
+ ]
137
+ })
138
+ ]
139
+ })
140
+ });
141
+ };
142
+ const Connecting = ({ oauthQuery, appName, email, assign }) => {
143
+ const { session, signOut } = useAuth();
144
+ const { path, basePath } = useRouter();
145
+ const go = goWith(assign);
146
+ const signedRequest = (url) => signedRequestFor(url, {
147
+ origin: window.location.origin,
148
+ pathname: `${basePath}${path}`
149
+ });
150
+ const [state, setState] = useState({ kind: "loading" });
151
+ const fail = (failure) => setState({
152
+ kind: "failed",
153
+ message: refusalOf(failure)
154
+ });
155
+ useEffect(() => {
156
+ let current = true;
157
+ const ask = async () => {
158
+ const { url } = await session.connect.resume(oauthQuery);
159
+ const query = signedRequest(url);
160
+ if (query === void 0) return go(url);
161
+ const params = new URLSearchParams(query);
162
+ const client = await session.connect.client(params.get("client_id") ?? "");
163
+ if (current) setState({
164
+ kind: "asking",
165
+ asking: {
166
+ oauthQuery: query,
167
+ client,
168
+ target: redirectTarget(params.get("redirect_uri") ?? "")
169
+ }
170
+ });
171
+ };
172
+ ask().catch((failure) => current && fail(failure));
173
+ return () => {
174
+ current = false;
175
+ };
176
+ }, [session, oauthQuery]);
177
+ const answer = async (accept, asking) => {
178
+ try {
179
+ const { url } = await session.connect.answer(accept, asking.oauthQuery);
180
+ if (signedRequest(url) !== void 0) await signOut();
181
+ go(url);
182
+ } catch (failure) {
183
+ fail(failure);
184
+ }
185
+ };
186
+ if (state.kind === "failed") return /* @__PURE__ */ jsx(Notice, {
187
+ title: "This app cannot be connected",
188
+ children: state.message
189
+ });
190
+ if (state.kind === "loading") return /* @__PURE__ */ jsxs("div", {
191
+ className: "flex min-h-48 items-center justify-center gap-2 text-muted-foreground",
192
+ children: [/* @__PURE__ */ jsx(Spinner, {}), " Loading"]
193
+ });
194
+ return /* @__PURE__ */ jsx(Request, {
195
+ asking: state.asking,
196
+ appName,
197
+ email,
198
+ onAnswer: (accept) => answer(accept, state.asking),
199
+ onSignOut: () => void signOut()
200
+ });
201
+ };
202
+ /**
203
+ * The connect page: someone signed in allows an outside app, such as an MCP client, to act as them, from the request
204
+ * the server signed and sent here. Inside another page's frame it shows only that it cannot be used there and calls
205
+ * nothing, whatever headers the host sent; Allow takes a click only once the page has been in front for a moment.
206
+ */
207
+ const ConnectPage = ({ email, appName, isFramed: isFramed$1 = isFramed, assign = (url) => window.location.assign(url) }) => {
208
+ const [framed] = useState(isFramed$1);
209
+ const { oauthQuery } = useConnectAddress();
210
+ if (framed) return /* @__PURE__ */ jsxs(Notice, {
211
+ title: "This page cannot be used inside another site",
212
+ children: [
213
+ /* @__PURE__ */ jsx("a", {
214
+ href: window.location.href,
215
+ target: "_blank",
216
+ rel: "noopener",
217
+ className: "underline underline-offset-2",
218
+ children: "Open it on its own"
219
+ }),
220
+ " ",
221
+ "to go on."
222
+ ]
223
+ });
224
+ if (!oauthQuery) return /* @__PURE__ */ jsx(Notice, {
225
+ title: "Nothing to connect",
226
+ children: "Connect again from your app."
227
+ });
228
+ return /* @__PURE__ */ jsx(Connecting, {
229
+ oauthQuery,
230
+ appName,
231
+ email,
232
+ assign
233
+ }, oauthQuery);
234
+ };
235
+ //#endregion
236
+ export { ConnectPage };