@agent-native/core 0.153.0 → 0.153.2

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 (38) hide show
  1. package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +21 -19
  2. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +3 -2
  3. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +3 -2
  4. package/corpus/templates/calendar/app/lib/event-layout.ts +16 -35
  5. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +27 -55
  6. package/corpus/templates/clips/app/components/player/share-dialog.tsx +104 -66
  7. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +45 -43
  8. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +0 -1
  9. package/corpus/templates/clips/desktop/src/app.tsx +818 -442
  10. package/corpus/templates/clips/desktop/src/styles.css +244 -2
  11. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +2 -2
  12. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  13. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +584 -1
  14. package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +25 -0
  15. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +1 -1
  16. package/corpus/templates/factory/app/routes/factory.tsx +0 -3
  17. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +1 -1
  18. package/corpus/templates/slides/app/components/editor/ShareDialog.tsx +28 -25
  19. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +50 -0
  20. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +12 -60
  21. package/dist/agent/production-agent.d.ts +8 -0
  22. package/dist/agent/production-agent.js +25 -2
  23. package/dist/agent/run-manager.js +13 -3
  24. package/dist/client/AgentPanel.js +6 -4
  25. package/dist/client/org/WorkspaceNotice.js +7 -4
  26. package/dist/client/sharing/ShareButton.d.ts +2 -2
  27. package/dist/client/sharing/ShareButton.js +18 -13
  28. package/dist/client/sharing/ShareDialog.d.ts +1 -1
  29. package/dist/client/sharing/ShareDialog.js +16 -11
  30. package/dist/collab/awareness.d.ts +2 -2
  31. package/dist/collab/struct-routes.d.ts +1 -1
  32. package/dist/resources/handlers.d.ts +1 -1
  33. package/dist/server/auth.js +18 -4
  34. package/dist/server/better-auth-instance.js +8 -1
  35. package/dist/server/magic-link-attribution.d.ts +21 -0
  36. package/dist/server/magic-link-attribution.js +115 -0
  37. package/dist/server/realtime-token.d.ts +1 -1
  38. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@agent-native/toolkit/ui";
2
3
  import * as Select from "@radix-ui/react-select";
3
4
  import { IconLock, IconWorld, IconTrash, IconCheck, IconChevronDown, IconCopy, IconLoader2, IconSearch, IconSearchOff, IconShare3, IconUsersGroup, } from "@tabler/icons-react";
4
5
  import { useEffect, useId, useRef, useState, } from "react";
@@ -114,25 +115,29 @@ function SharePanel(props) {
114
115
  (Boolean(props.shareUrl) ||
115
116
  Boolean(props.shareUrlPlaceholder) ||
116
117
  Boolean(props.secondaryShareUrl));
117
- const shareUrlPlacement = props.shareUrlPlacement ?? "bottom";
118
+ const shareUrlPlacement = props.shareUrlPlacement ?? "top";
119
+ const [advancedOpen, setAdvancedOpen] = useState(!showShareLinks);
120
+ useEffect(() => {
121
+ setAdvancedOpen(!showShareLinks);
122
+ }, [props.resourceId, showShareLinks]);
118
123
  const extraTabs = props.shareTabs?.tabs ?? [];
119
124
  const hasTabs = extraTabs.length > 0;
120
125
  const shareTabLabel = props.shareTabs?.shareLabel ?? "Share link";
121
126
  const titleText = resourceTitle
122
127
  ? `Share "${resourceTitle}"`
123
128
  : `Share ${props.resourceType}`;
124
- const sharePanel = isLoading ? (_jsxs("div", { children: [!hasTabs ? (_jsx("div", { className: "mb-3 truncate text-base font-semibold", title: titleText, children: titleText })) : null, _jsx("div", { className: "mb-4 h-9 rounded-md bg-muted animate-pulse" }), _jsx("div", { className: "mb-2 text-sm font-semibold", children: peopleAccessLabel }), _jsx("div", { className: "mb-4 h-7 rounded-md bg-muted animate-pulse" }), _jsx("div", { className: "mb-2 text-sm font-semibold", children: generalAccessLabel }), _jsx("div", { className: "mb-4 h-9 rounded-md bg-muted animate-pulse" })] })) : (_jsxs("div", { children: [!hasTabs ? (_jsx("div", { className: "mb-3 truncate text-base font-semibold", title: titleText, children: titleText })) : null, showShareLinks && shareUrlPlacement === "top" ? shareLinks : null, canManage ? (_jsxs("div", { className: "mb-4 space-y-2", children: [_jsxs("div", { className: "flex items-stretch gap-2", children: [_jsx(MemberAutocomplete, { value: inviteEmail, open: suggestionsOpen, onOpenChange: setSuggestionsOpen, onValueChange: (next) => {
125
- onInviteEmailChange(next);
126
- if (shareError)
127
- setShareError(null);
128
- }, onSelectMember: (member) => {
129
- onInviteEmailChange(member.email);
130
- setSuggestionsOpen(false);
131
- if (shareError)
132
- setShareError(null);
133
- }, onSubmit: handleAdd, placeholder: policy.requireOrgMemberForUserShares
134
- ? "Add people from your organization"
135
- : "Add people by email", suggestions: memberSuggestions, search: memberSearch }), _jsx(RoleSelect, { value: role, onChange: setRole, roleCopy: props.roleCopy, allowedRoles: props.allowedRoles }), _jsx("button", { type: "button", onClick: handleAdd, disabled: !hasInviteEmail, className: BUTTON_PRIMARY_SM, children: "Add" })] }), shareError ? (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: shareError })) : null, hasInviteEmail ? (_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 text-xs text-muted-foreground", children: [_jsxs("label", { className: "inline-flex items-center gap-2", children: [_jsx("input", { type: "checkbox", checked: notifyPeople, onChange: (e) => setNotifyPeople(e.target.checked), className: "h-4 w-4 rounded border-input accent-primary" }), "Notify people"] }), notifyPeople ? (_jsx("button", { type: "button", "aria-expanded": messageOpen, onClick: () => setMessageOpen(!messageOpen), className: "rounded-sm px-1 py-0.5 font-medium text-foreground underline decoration-border underline-offset-2 transition-colors hover:bg-accent hover:text-accent-foreground", children: messageOpen ? "Hide message" : "Add a message" })) : null] })) : null, hasInviteEmail && notifyPeople && messageOpen ? (_jsx("div", { className: "rounded-md border border-border/70 bg-muted/20 p-2.5", children: _jsx("textarea", { "aria-label": "Message", placeholder: "Add a short note (optional)", value: shareMessage, onChange: (event) => setShareMessage(event.target.value), maxLength: 500, rows: 3, className: "w-full resize-y rounded-md border border-input bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background" }) })) : null] })) : null, _jsx("div", { className: "mb-2 text-sm font-semibold", children: peopleAccessLabel }), _jsxs("ul", { className: "mb-4 flex flex-col gap-1 list-none p-0 m-0", children: [data?.ownerEmail ? (_jsxs("li", { className: "flex items-center gap-3 px-1 py-1.5 text-sm", children: [_jsx(Avatar, { label: displayName(data.ownerEmail, knownMembers) }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: displayName(data.ownerEmail, knownMembers) }), _jsx("span", { className: "text-xs text-muted-foreground", children: "Owner" })] })) : null, shares.map((s) => (_jsxs("li", { className: cn("flex items-center gap-3 px-1 py-1.5 text-sm", inFlight.has(keyOf(s)) && "opacity-60"), children: [_jsx(Avatar, { label: principalLabel(s, knownMembers), org: s.principalType === "org" }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: principalLabel(s, knownMembers) }), canManage ? (_jsx(RoleSelect, { value: s.role, onChange: (r) => handleChangeRole(s, r), disabled: inFlight.has(keyOf(s)), plain: true, roleCopy: props.roleCopy, allowedRoles: props.allowedRoles })) : (_jsx("span", { className: "text-xs text-muted-foreground", children: roleMeta(s.role, props.roleCopy).label })), canManage ? (_jsx("button", { type: "button", "aria-label": "Remove", onClick: () => handleRemove(s), disabled: inFlight.has(keyOf(s)), className: BUTTON_GHOST_ICON, children: _jsx(IconTrash, { size: 14 }) })) : null] }, keyOf(s)))), !shares.length && !data?.ownerEmail ? (_jsx("li", { className: "px-1 py-1.5 text-sm text-muted-foreground", children: "No one has access yet." })) : null] }), _jsx("div", { className: "mb-2 text-sm font-semibold", children: generalAccessLabel }), _jsxs("div", { className: "mb-4 flex items-center gap-3", children: [_jsx("span", { "aria-hidden": true, className: "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(meta.Icon, { size: 16, strokeWidth: 1.75 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(VisibilitySelect, { value: visibility, onChange: handleVisibility, disabled: !canManage, visibilityCopy: props.visibilityCopy, allowPublic: policy.allowPublic }), _jsxs("div", { className: "mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground", children: [_jsx("span", { children: meta.description }), visibility === "org" && props.hideInSearchControl ? (_jsx(AdvancedAccessPopover, { control: props.hideInSearchControl, canManage: canManage, onToggle: handleHideInSearch })) : null] })] })] }), shareError && !canManage ? (_jsx("div", { role: "alert", className: "mb-4 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: shareError })) : null, props.accessNote ? (_jsx("div", { className: "mb-4 rounded-md border border-border bg-muted/35 p-3 text-xs text-muted-foreground", children: props.accessNote })) : null, showShareLinks && shareUrlPlacement === "bottom" ? shareLinks : null, props.shareFooterContent] }));
129
+ const sharePanel = isLoading ? (_jsxs("div", { children: [!hasTabs ? (_jsx("div", { className: "mb-3 truncate text-base font-semibold", title: titleText, children: titleText })) : null, _jsx("div", { className: "mb-4 h-9 rounded-md bg-muted animate-pulse" }), _jsx("div", { className: "mb-2 text-sm font-semibold", children: peopleAccessLabel }), _jsx("div", { className: "mb-4 h-7 rounded-md bg-muted animate-pulse" }), _jsx("div", { className: "mb-2 text-sm font-semibold", children: generalAccessLabel }), _jsx("div", { className: "mb-4 h-9 rounded-md bg-muted animate-pulse" })] })) : (_jsxs("div", { children: [!hasTabs ? (_jsx("div", { className: "mb-3 truncate text-base font-semibold", title: titleText, children: titleText })) : null, showShareLinks && shareUrlPlacement === "top" ? shareLinks : null, _jsxs(Collapsible, { open: advancedOpen, onOpenChange: setAdvancedOpen, className: "mb-4 overflow-hidden rounded-md border border-border", children: [_jsx(CollapsibleTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex min-h-10 w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm font-medium transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2 truncate", children: [_jsx(IconUsersGroup, { "aria-hidden": true, size: 15, strokeWidth: 1.8, className: "shrink-0 text-muted-foreground" }), canManage ? "Manage access" : peopleAccessLabel] }), _jsx(IconChevronDown, { "aria-hidden": true, size: 16, strokeWidth: 1.8, className: cn("shrink-0 text-muted-foreground transition-transform", advancedOpen && "rotate-180") })] }) }), _jsx(CollapsibleContent, { className: "border-t border-border px-3 py-3", children: _jsxs("div", { className: "space-y-4", children: [canManage ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-stretch gap-2", children: [_jsx(MemberAutocomplete, { value: inviteEmail, open: suggestionsOpen, onOpenChange: setSuggestionsOpen, onValueChange: (next) => {
130
+ onInviteEmailChange(next);
131
+ if (shareError)
132
+ setShareError(null);
133
+ }, onSelectMember: (member) => {
134
+ onInviteEmailChange(member.email);
135
+ setSuggestionsOpen(false);
136
+ if (shareError)
137
+ setShareError(null);
138
+ }, onSubmit: handleAdd, placeholder: policy.requireOrgMemberForUserShares
139
+ ? "Add people from your organization"
140
+ : "Add people by email", suggestions: memberSuggestions, search: memberSearch }), _jsx(RoleSelect, { value: role, onChange: setRole, roleCopy: props.roleCopy, allowedRoles: props.allowedRoles }), _jsx("button", { type: "button", onClick: handleAdd, disabled: !hasInviteEmail, className: BUTTON_PRIMARY_SM, children: "Add" })] }), shareError ? (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: shareError })) : null, hasInviteEmail ? (_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 text-xs text-muted-foreground", children: [_jsxs("label", { className: "inline-flex items-center gap-2", children: [_jsx("input", { type: "checkbox", checked: notifyPeople, onChange: (e) => setNotifyPeople(e.target.checked), className: "h-4 w-4 rounded border-input accent-primary" }), "Notify people"] }), notifyPeople ? (_jsx("button", { type: "button", "aria-expanded": messageOpen, onClick: () => setMessageOpen(!messageOpen), className: "rounded-sm px-1 py-0.5 font-medium text-foreground underline decoration-border underline-offset-2 transition-colors hover:bg-accent hover:text-accent-foreground", children: messageOpen ? "Hide message" : "Add a message" })) : null] })) : null, hasInviteEmail && notifyPeople && messageOpen ? (_jsx("div", { className: "rounded-md border border-border/70 bg-muted/20 p-2.5", children: _jsx("textarea", { "aria-label": "Message", placeholder: "Add a short note (optional)", value: shareMessage, onChange: (event) => setShareMessage(event.target.value), maxLength: 500, rows: 3, className: "w-full resize-y rounded-md border border-input bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background" }) })) : null] })) : null, _jsxs("div", { children: [_jsx("div", { className: "mb-2 text-sm font-semibold", children: peopleAccessLabel }), _jsxs("ul", { className: "flex flex-col gap-1 list-none p-0 m-0", children: [data?.ownerEmail ? (_jsxs("li", { className: "flex items-center gap-3 px-1 py-1.5 text-sm", children: [_jsx(Avatar, { label: displayName(data.ownerEmail, knownMembers) }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: displayName(data.ownerEmail, knownMembers) }), _jsx("span", { className: "text-xs text-muted-foreground", children: "Owner" })] })) : null, shares.map((s) => (_jsxs("li", { className: cn("flex items-center gap-3 px-1 py-1.5 text-sm", inFlight.has(keyOf(s)) && "opacity-60"), children: [_jsx(Avatar, { label: principalLabel(s, knownMembers), org: s.principalType === "org" }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: principalLabel(s, knownMembers) }), canManage ? (_jsx(RoleSelect, { value: s.role, onChange: (r) => handleChangeRole(s, r), disabled: inFlight.has(keyOf(s)), plain: true, roleCopy: props.roleCopy, allowedRoles: props.allowedRoles })) : (_jsx("span", { className: "text-xs text-muted-foreground", children: roleMeta(s.role, props.roleCopy).label })), canManage ? (_jsx("button", { type: "button", "aria-label": "Remove", onClick: () => handleRemove(s), disabled: inFlight.has(keyOf(s)), className: BUTTON_GHOST_ICON, children: _jsx(IconTrash, { size: 14 }) })) : null] }, keyOf(s)))), !shares.length && !data?.ownerEmail ? (_jsx("li", { className: "px-1 py-1.5 text-sm text-muted-foreground", children: "No one has access yet." })) : null] })] })] }) })] }), _jsx("div", { className: "mb-2 text-sm font-semibold", children: generalAccessLabel }), _jsxs("div", { className: "mb-4 flex items-center gap-3", children: [_jsx("span", { "aria-hidden": true, className: "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(meta.Icon, { size: 16, strokeWidth: 1.75 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(VisibilitySelect, { value: visibility, onChange: handleVisibility, disabled: !canManage, visibilityCopy: props.visibilityCopy, allowPublic: policy.allowPublic }), _jsxs("div", { className: "mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground", children: [_jsx("span", { className: "sr-only", children: meta.description }), visibility === "org" && props.hideInSearchControl ? (_jsx(AdvancedAccessPopover, { control: props.hideInSearchControl, canManage: canManage, onToggle: handleHideInSearch })) : null] })] })] }), shareError && !canManage ? (_jsx("div", { role: "alert", className: "mb-4 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: shareError })) : null, props.accessNote ? (_jsx("div", { className: "mb-4 rounded-md border border-border bg-muted/35 p-3 text-xs text-muted-foreground", children: props.accessNote })) : null, showShareLinks && shareUrlPlacement === "bottom" ? shareLinks : null, props.shareFooterContent] }));
136
141
  if (!hasTabs)
137
142
  return sharePanel;
138
143
  const tabs = [
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from "react";
1
+ import { type ReactNode } from "react";
2
2
  export interface ShareDialogProps {
3
3
  open: boolean;
4
4
  onClose: () => void;
@@ -1,6 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ActionButton, Avatar as DesignSystemAvatar, Dialog as DesignSystemDialog, IconButton, Picker, Status, TextArea, TextField, } from "@agent-native/toolkit/design-system";
3
- import { IconCheck, IconCode, IconCopy, IconLink, IconLock, IconMail, IconTrash, IconUsersGroup, IconWorld, } from "@tabler/icons-react";
3
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@agent-native/toolkit/ui";
4
+ import { IconCheck, IconCode, IconCopy, IconChevronDown, IconLink, IconLock, IconMail, IconTrash, IconUsersGroup, IconWorld, } from "@tabler/icons-react";
5
+ import { useState } from "react";
4
6
  import { cn } from "../utils.js";
5
7
  import { useShareDialogController, } from "./useShareDialogController.js";
6
8
  const BUTTON_BASE = "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors active:!scale-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0";
@@ -35,7 +37,9 @@ export function ShareDialog({ open, onClose, resourceType, resourceId, resourceT
35
37
  });
36
38
  if (!open)
37
39
  return null;
38
- return (_jsxs(DesignSystemDialog, { open: controller.open, onOpenChange: controller.onOpenChange, title: _jsx("span", { className: "truncate !text-base !leading-normal !tracking-normal !text-inherit", title: controller.title, children: controller.title }), closeLabel: controller.labels.close, size: "large", className: "!top-4 !z-[2010] !block !max-h-none !w-[calc(100vw-2rem)] !max-w-lg !translate-y-0 !gap-0 !overflow-visible !rounded-xl !border-border !bg-popover !p-0 !text-popover-foreground !shadow-2xl sm:!top-1/2 sm:!-translate-y-1/2", "aria-label": controller.title, children: [_jsx("div", { className: "px-5 pt-0 pb-3", children: controller.ownerLabel ? (_jsx("div", { className: "truncate text-xs text-muted-foreground", children: controller.ownerLabel })) : null }), controller.tabsEnabled ? (_jsx("div", { role: "tablist", "aria-label": controller.labels.shareOptions, className: "mx-5 mt-1 flex gap-1 border-b border-border", children: controller.tabs.map((tab) => {
40
+ return (_jsxs(DesignSystemDialog, { open: controller.open, onOpenChange: controller.onOpenChange, title: _jsx("span", { className: cn(controller.tabsEnabled
41
+ ? "sr-only"
42
+ : "truncate !text-base !leading-normal !tracking-normal !text-inherit"), title: controller.title, children: controller.title }), closeLabel: controller.labels.close, size: "large", className: "!top-4 !z-[2010] !block !max-h-none !w-[calc(100vw-2rem)] !max-w-lg !translate-y-0 !gap-0 !overflow-visible !rounded-xl !border-border !bg-popover !p-0 !text-popover-foreground !shadow-2xl sm:!top-1/2 sm:!-translate-y-1/2", "aria-label": controller.title, children: [!controller.tabsEnabled && controller.ownerLabel ? (_jsx("div", { className: "px-5 pt-0 pb-3", children: _jsx("div", { className: "truncate text-xs text-muted-foreground", children: controller.ownerLabel }) })) : null, controller.tabsEnabled ? (_jsx("div", { role: "tablist", "aria-label": controller.labels.shareOptions, className: "mx-5 mt-1 flex gap-1 border-b border-border", children: controller.tabs.map((tab) => {
39
43
  const Icon = TAB_ICONS[tab.value];
40
44
  return (_jsx(TabTrigger, { active: controller.activeTab === tab.value, onClick: () => controller.setActiveTab(tab.value), icon: _jsx(Icon, { size: 14, strokeWidth: 1.75 }), label: tab.label }, tab.value));
41
45
  }) })) : null, _jsxs("div", { className: "px-5 py-4", children: [controller.tabsEnabled && controller.activeTab === "link" ? (_jsx(LinkTab, { controller: controller, extras: linkTabExtras })) : null, !controller.tabsEnabled || controller.activeTab === "invite" ? (_jsx(InviteTab, { controller: controller, showVisibility: !controller.tabsEnabled })) : null, controller.tabsEnabled && controller.activeTab === "embed"
@@ -49,18 +53,19 @@ function TabTrigger({ active, onClick, icon, label, }) {
49
53
  }
50
54
  function LinkTab({ controller, extras, }) {
51
55
  const Icon = VIS_ICONS[controller.visibility.value];
52
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("div", { className: "mb-2 text-sm font-semibold", children: controller.labels.generalAccess }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { "aria-hidden": true, className: "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(Icon, { size: 16, strokeWidth: 1.75 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(VisibilitySelect, { controller: controller }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground", children: controller.visibility.description })] })] })] }), _jsx(CopyField, { field: "share-link", label: controller.labels.shareLink, value: controller.shareUrl, controller: controller }), extras] }));
56
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx(CopyField, { field: "share-link", label: controller.labels.shareLink, value: controller.shareUrl, controller: controller }), _jsxs("div", { children: [_jsx("div", { className: "mb-2 text-xs font-medium text-muted-foreground", children: controller.labels.generalAccess }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { "aria-hidden": true, className: "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(Icon, { size: 16, strokeWidth: 1.75 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(VisibilitySelect, { controller: controller }), _jsx("div", { className: "sr-only text-xs text-muted-foreground", children: controller.visibility.description })] })] })] }), extras] }));
53
57
  }
54
58
  function InviteTab({ controller, showVisibility, }) {
55
59
  const Icon = VIS_ICONS[controller.visibility.value];
56
- return (_jsxs("div", { className: "space-y-4", children: [controller.canManage ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-stretch gap-2", children: [_jsx("input", { type: "email", placeholder: controller.labels.addPeopleByEmail, value: controller.invite.email, onChange: (event) => controller.invite.setEmail(event.currentTarget.value), onKeyDown: (event) => {
57
- if (event.key === "Enter")
58
- controller.invite.submit();
59
- }, autoComplete: "off", className: "flex-1 min-w-0 h-9 rounded-md border border-input bg-background px-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background" }), _jsx(RoleSelect, { controller: controller })] }), controller.invite.showNotifyPeople ? (_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 text-xs text-muted-foreground", children: [_jsxs("label", { className: "inline-flex items-center gap-2", children: [_jsx("input", { type: "checkbox", checked: controller.invite.notifyPeople, onChange: (event) => controller.invite.setNotifyPeople(event.currentTarget.checked), className: "h-4 w-4 rounded border-input accent-primary" }), controller.labels.notifyPeople] }), controller.invite.notifyPeople ? (_jsx(ActionButton, { type: "button", emphasis: "ghost", "aria-expanded": controller.invite.messageOpen, onPress: () => controller.invite.setMessageOpen(!controller.invite.messageOpen), className: "!h-auto !rounded-sm !px-1 !py-0.5 text-xs font-medium !text-foreground underline decoration-border underline-offset-2 hover:!bg-accent hover:!text-accent-foreground active:!scale-100", children: controller.invite.messageOpen
60
- ? controller.labels.hideMessage
61
- : controller.labels.addMessage })) : null] })) : null, controller.invite.showNotifyPeople &&
62
- controller.invite.notifyPeople &&
63
- controller.invite.messageOpen ? (_jsx(TextArea, { "aria-label": controller.labels.addMessage, placeholder: controller.labels.messagePlaceholder, value: controller.invite.message, onChange: (value) => controller.invite.setMessage(value), maxLength: 500, rows: 3, className: "min-h-20 resize-y text-sm" })) : null] })) : null, _jsxs("div", { children: [_jsx("div", { className: "mb-2 text-sm font-semibold", children: controller.labels.peopleWithAccess }), _jsxs("ul", { className: "flex flex-col gap-1 list-none p-0 m-0", children: [controller.people.map((person) => (_jsx(PersonRow, { person: person, canManage: controller.canManage, removeLabel: controller.labels.remove, onRemove: controller.removeShare }, person.key))), !controller.people.length ? (_jsx("li", { className: "px-1 py-1.5 text-sm text-muted-foreground", children: controller.labels.noAccess })) : null] })] }), showVisibility ? (_jsxs("div", { children: [_jsx("div", { className: "mb-2 text-sm font-semibold", children: controller.labels.generalAccess }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { "aria-hidden": true, className: "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(Icon, { size: 16, strokeWidth: 1.75 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(VisibilitySelect, { controller: controller }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground", children: controller.visibility.description })] })] })] })) : null] }));
60
+ const [advancedOpen, setAdvancedOpen] = useState(false);
61
+ return (_jsxs("div", { className: "space-y-3", children: [_jsxs(Collapsible, { open: advancedOpen, onOpenChange: setAdvancedOpen, className: "overflow-hidden rounded-md border border-border", children: [_jsx(CollapsibleTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex min-h-10 w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm font-medium transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", children: [_jsx("span", { className: "truncate", children: controller.labels.peopleWithAccess }), _jsx(IconChevronDown, { "aria-hidden": true, size: 16, strokeWidth: 1.8, className: cn("shrink-0 text-muted-foreground transition-transform", advancedOpen && "rotate-180") })] }) }), _jsx(CollapsibleContent, { className: "border-t border-border px-3 py-3", children: _jsxs("div", { className: "space-y-4", children: [controller.canManage ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-stretch gap-2", children: [_jsx("input", { type: "email", placeholder: controller.labels.addPeopleByEmail, value: controller.invite.email, onChange: (event) => controller.invite.setEmail(event.currentTarget.value), onKeyDown: (event) => {
62
+ if (event.key === "Enter")
63
+ controller.invite.submit();
64
+ }, autoComplete: "off", className: "h-9 min-w-0 flex-1 rounded-md border border-input bg-background px-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background" }), _jsx(RoleSelect, { controller: controller })] }), controller.invite.showNotifyPeople ? (_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 text-xs text-muted-foreground", children: [_jsxs("label", { className: "inline-flex items-center gap-2", children: [_jsx("input", { type: "checkbox", checked: controller.invite.notifyPeople, onChange: (event) => controller.invite.setNotifyPeople(event.currentTarget.checked), className: "h-4 w-4 rounded border-input accent-primary" }), controller.labels.notifyPeople] }), controller.invite.notifyPeople ? (_jsx(ActionButton, { type: "button", emphasis: "ghost", "aria-expanded": controller.invite.messageOpen, onPress: () => controller.invite.setMessageOpen(!controller.invite.messageOpen), className: "!h-auto !rounded-sm !px-1 !py-0.5 text-xs font-medium !text-foreground underline decoration-border underline-offset-2 hover:!bg-accent hover:!text-accent-foreground active:!scale-100", children: controller.invite.messageOpen
65
+ ? controller.labels.hideMessage
66
+ : controller.labels.addMessage })) : null] })) : null, controller.invite.showNotifyPeople &&
67
+ controller.invite.notifyPeople &&
68
+ controller.invite.messageOpen ? (_jsx(TextArea, { "aria-label": controller.labels.addMessage, placeholder: controller.labels.messagePlaceholder, value: controller.invite.message, onChange: (value) => controller.invite.setMessage(value), maxLength: 500, rows: 3, className: "min-h-20 resize-y text-sm" })) : null] })) : null, _jsxs("div", { children: [_jsx("div", { className: "mb-2 text-sm font-semibold", children: controller.labels.peopleWithAccess }), _jsxs("ul", { className: "m-0 flex list-none flex-col gap-1 p-0", children: [controller.people.map((person) => (_jsx(PersonRow, { person: person, canManage: controller.canManage, removeLabel: controller.labels.remove, onRemove: controller.removeShare }, person.key))), !controller.people.length ? (_jsx("li", { className: "px-1 py-1.5 text-sm text-muted-foreground", children: controller.labels.noAccess })) : null] })] })] }) })] }), showVisibility ? (_jsxs("div", { children: [_jsx("div", { className: "mb-2 text-sm font-semibold", children: controller.labels.generalAccess }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { "aria-hidden": true, className: "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(Icon, { size: 16, strokeWidth: 1.75 }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(VisibilitySelect, { controller: controller }), _jsx("div", { className: "sr-only text-xs text-muted-foreground", children: controller.visibility.description })] })] })] })) : null] }));
64
69
  }
65
70
  function PersonRow({ person, canManage, removeLabel, onRemove, }) {
66
71
  return (_jsxs("li", { className: "flex items-center gap-3 px-1 py-1.5 text-sm", children: [_jsx(DesignSystemAvatar, { name: person.label, fallback: person.principalType === "org" ? (_jsx(IconUsersGroup, { size: 14, strokeWidth: 1.75 })) : (person.avatarText), size: "compact", className: "inline-flex h-7 w-7 shrink-0 text-[11px] font-semibold" }), _jsx("span", { className: "flex-1 min-w-0 truncate", children: person.label }), _jsx(Status, { tone: "neutral", size: "compact", className: "border-0 bg-transparent px-0 text-xs text-muted-foreground", children: person.roleLabel }), canManage && person.share ? (_jsx(IconButton, { intent: "danger", emphasis: "ghost", size: "compact", icon: _jsx(IconTrash, { size: 14 }), label: removeLabel, "aria-label": removeLabel, onPress: () => onRemove(person.share), className: cn(BUTTON_GHOST_ICON, "[&_svg]:!size-auto") })) : null] }));
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  states: {
66
67
  clientId: number;
67
68
  state: string;
68
69
  }[];
69
- error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
+ error?: undefined;
80
81
  users: {
81
82
  clientId: number;
82
83
  lastSeen: number;
83
84
  }[];
84
- error?: undefined;
85
85
  }>>;
@@ -13,8 +13,8 @@
13
13
  * Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
14
14
  */
15
15
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- error: string;
17
16
  ok?: undefined;
17
+ error: string;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
48
48
  }>;
49
49
  /** DELETE /_agent-native/resources/:id — delete a resource */
50
50
  export declare function handleDeleteResource(event: any): Promise<{
51
- ok?: undefined;
52
51
  error: string;
52
+ ok?: undefined;
53
53
  } | {
54
54
  error?: undefined;
55
55
  ok: boolean;
@@ -56,9 +56,9 @@ import { isValidWorkspaceAppIdFormat } from "../shared/workspace-app-id.js";
56
56
  import { injectAnalyticsIntoHtml } from "./analytics.js";
57
57
  import { getConfiguredAppBasePath } from "./app-base-path.js";
58
58
  import { getAppProductionUrl } from "./app-url.js";
59
- import { readAnalyticsAnonymousId, signupAttributionFromCookieHeader, } from "./attribution.js";
59
+ import { readAnalyticsAnonymousId, readFirstTouchAttribution, signupAttributionFromCookieHeader, } from "./attribution.js";
60
60
  import { getAuthLoginMode } from "./auth-login-mode.js";
61
- import { createBetterAuthSessionForEmail, ensureGoogleAuthIdentity, getBetterAuth, getBetterAuthSync, isDeployPreview, } from "./better-auth-instance.js";
61
+ import { createBetterAuthSessionForEmail, ensureGoogleAuthIdentity, getAuthSecret, getBetterAuth, getBetterAuthSync, isDeployPreview, } from "./better-auth-instance.js";
62
62
  import { BUILDER_CONNECT_OWNER_COOKIE, BUILDER_CONNECT_PARAM, BUILDER_RELAY_PATH, BUILDER_RELAY_STATE_PARAM, BUILDER_STATE_PARAM, verifyBuilderCallbackStateAndGetOwner, verifyBuilderConnectTokenAndGetOwner, verifyBuilderPreviewRelayStateForCallback, } from "./builder-browser.js";
63
63
  import { resolveAuthCookieNamespace } from "./cookie-namespace.js";
64
64
  import { getAllowedCorsOrigin, readCorsAllowedOrigins, } from "./cors-origins.js";
@@ -67,6 +67,7 @@ import { resolveGoogleSignInCredentials } from "./google-oauth-credentials.js";
67
67
  import { isElectron as isElectronRequest, getAppBasePath, getAppUrl, getOrigin, encodeOAuthState, decodeOAuthState, createOAuthSession, oauthCallbackResponse, oauthDesktopExchangePage, oauthErrorPage, resolveOAuthRedirectUri, isAllowedOAuthRedirectUri, } from "./google-oauth.js";
68
68
  import { isCanonicalAgentNativeAppRequest, isDesktopSsoCanaryUserAgent, isIdentitySsoEnabled, } from "./identity-sso-store.js";
69
69
  import { ensureCanonicalUserForLegacySession } from "./legacy-auth-migration.js";
70
+ import { encodeMagicLinkSignupAttribution, MAGIC_LINK_ATTRIBUTION_PARAM, } from "./magic-link-attribution.js";
70
71
  import { safeOAuthReturnUrl } from "./oauth-return-url.js";
71
72
  import { getOnboardingHtml, getResetPasswordHtml, } from "./onboarding-html.js";
72
73
  import { captureAuthError } from "./sentry.js";
@@ -3333,12 +3334,25 @@ async function mountBetterAuthRoutes(app, options) {
3333
3334
  callbackPath = withDesktopMagicLinkFlow(callbackPath, desktopFlow);
3334
3335
  }
3335
3336
  const callbackURL = betterAuthCallbackURL(callbackPath, true, event);
3336
- const newUserCallbackPath = `${getAppBasePath()}/_agent-native/auth/magic-link/new-user?return=${encodeURIComponent(callbackPath)}`;
3337
+ const cookieHeader = getHeader(event, "cookie") ?? null;
3338
+ const signupAttribution = signupAttributionFromCookieHeader(cookieHeader);
3339
+ const signupAnonymousId = readAnalyticsAnonymousId(cookieHeader);
3340
+ const hasSignupAttribution = !!readFirstTouchAttribution(cookieHeader) || !!signupAnonymousId;
3341
+ const attributionToken = hasSignupAttribution
3342
+ ? encodeMagicLinkSignupAttribution({
3343
+ attribution: signupAttribution,
3344
+ anonymousId: signupAnonymousId,
3345
+ }, getAuthSecret())
3346
+ : undefined;
3347
+ const newUserCallbackUrl = new URL(`${getAppBasePath()}/_agent-native/auth/magic-link/new-user?return=${encodeURIComponent(callbackPath)}`, getOrigin(event));
3348
+ if (attributionToken) {
3349
+ newUserCallbackUrl.searchParams.set(MAGIC_LINK_ATTRIBUTION_PARAM, attributionToken);
3350
+ }
3337
3351
  await auth.api.signInMagicLink({
3338
3352
  body: {
3339
3353
  email,
3340
3354
  callbackURL,
3341
- newUserCallbackURL: betterAuthCallbackURL(newUserCallbackPath, true, event),
3355
+ newUserCallbackURL: betterAuthCallbackURL(`${newUserCallbackUrl.pathname}${newUserCallbackUrl.search}`, true, event),
3342
3356
  },
3343
3357
  headers: event.headers,
3344
3358
  });
@@ -32,6 +32,7 @@ import { getWorkspaceA2ADerivedSecret } from "./derived-secret.js";
32
32
  import { renderMagicLinkEmail, renderResetPasswordEmail, renderVerifySignupEmail, } from "./email-templates.js";
33
33
  import { getEmailReadiness, sendEmail } from "./email.js";
34
34
  import { resolveGoogleSignInCredentials } from "./google-oauth-credentials.js";
35
+ import { readMagicLinkSignupAttribution } from "./magic-link-attribution.js";
35
36
  export { getAuthLoginMode, resolveAuthLoginMode, resolveAuthLoginModeFromReadiness, } from "./auth-login-mode.js";
36
37
  async function flushSignupTracking() {
37
38
  try {
@@ -982,8 +983,14 @@ async function createBetterAuthInstance(config) {
982
983
  const cookieHeader = context?.headers?.get("cookie") ??
983
984
  context?.request?.headers?.get("cookie") ??
984
985
  null;
986
+ const magicLinkAttribution = context?.request?.url?.includes("newUserCallbackURL")
987
+ ? readMagicLinkSignupAttribution(context.request.url, getAuthSecret())
988
+ : undefined;
985
989
  attribution = signupAttributionFromCookieHeader(cookieHeader);
986
- anonymousId = readAnalyticsAnonymousId(cookieHeader);
990
+ attribution = magicLinkAttribution?.attribution ?? attribution;
991
+ anonymousId =
992
+ magicLinkAttribution?.anonymousId ??
993
+ readAnalyticsAnonymousId(cookieHeader);
987
994
  }
988
995
  catch (err) {
989
996
  console.error("[auth] failed to derive signup attribution", err);
@@ -0,0 +1,21 @@
1
+ /** Query parameter carried by Better Auth's new-user callback URL. */
2
+ export declare const MAGIC_LINK_ATTRIBUTION_PARAM = "signup_attribution";
3
+ export interface MagicLinkSignupAttribution {
4
+ attribution?: Record<string, string>;
5
+ anonymousId?: string;
6
+ }
7
+ /**
8
+ * Sign the attribution captured when a magic link is requested. The token is
9
+ * analytics context only; it never authorizes the user or changes the return
10
+ * URL. The Better Auth secret keeps the callback from accepting forged data.
11
+ */
12
+ export declare function encodeMagicLinkSignupAttribution(value: MagicLinkSignupAttribution, secret: string, now?: number): string | undefined;
13
+ /** Verify and decode a token previously produced by
14
+ * {@link encodeMagicLinkSignupAttribution}. */
15
+ export declare function decodeMagicLinkSignupAttribution(token: string | null | undefined, secret: string, now?: number): MagicLinkSignupAttribution | null | undefined;
16
+ /**
17
+ * Read the signed handoff from Better Auth's magic-link verification request.
18
+ * Better Auth copies `newUserCallbackURL` into the verification URL, so this
19
+ * remains available even when the link is opened on another device.
20
+ */
21
+ export declare function readMagicLinkSignupAttribution(requestUrl: string | undefined, secret: string, now?: number): MagicLinkSignupAttribution | null | undefined;
@@ -0,0 +1,115 @@
1
+ import crypto from "node:crypto";
2
+ import { normalizeAnalyticsAnonymousId } from "../shared/analytics-anonymous-id.js";
3
+ /** Query parameter carried by Better Auth's new-user callback URL. */
4
+ export const MAGIC_LINK_ATTRIBUTION_PARAM = "signup_attribution";
5
+ /** Better Auth's magic-link token is valid for five minutes; keep this handoff
6
+ * slightly longer so clock skew does not discard a still-valid link. */
7
+ const MAGIC_LINK_ATTRIBUTION_TTL_SECONDS = 10 * 60;
8
+ const MAX_ATTRIBUTION_FIELDS = 32;
9
+ const MAX_ATTRIBUTION_VALUE_LENGTH = 200;
10
+ function sanitizeAttribution(value) {
11
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
12
+ return undefined;
13
+ }
14
+ const out = {};
15
+ for (const [key, raw] of Object.entries(value).slice(0, MAX_ATTRIBUTION_FIELDS)) {
16
+ if (!/^[a-z][a-z0-9_]{0,63}$/.test(key) ||
17
+ typeof raw !== "string" ||
18
+ raw.length === 0) {
19
+ continue;
20
+ }
21
+ out[key] = raw.slice(0, MAX_ATTRIBUTION_VALUE_LENGTH);
22
+ }
23
+ return Object.keys(out).length > 0 ? out : undefined;
24
+ }
25
+ function encodePayload(payload) {
26
+ return Buffer.from(JSON.stringify(payload)).toString("base64url");
27
+ }
28
+ /**
29
+ * Sign the attribution captured when a magic link is requested. The token is
30
+ * analytics context only; it never authorizes the user or changes the return
31
+ * URL. The Better Auth secret keeps the callback from accepting forged data.
32
+ */
33
+ export function encodeMagicLinkSignupAttribution(value, secret, now = Date.now()) {
34
+ if (!secret)
35
+ return undefined;
36
+ const attribution = sanitizeAttribution(value.attribution);
37
+ const anonymousId = normalizeAnalyticsAnonymousId(value.anonymousId);
38
+ if (!attribution && !anonymousId)
39
+ return undefined;
40
+ const payload = {
41
+ exp: Math.floor(now / 1000) + MAGIC_LINK_ATTRIBUTION_TTL_SECONDS,
42
+ ...(attribution ? { attribution } : {}),
43
+ ...(anonymousId ? { anonymousId } : {}),
44
+ };
45
+ const data = encodePayload(payload);
46
+ const signature = crypto
47
+ .createHmac("sha256", secret)
48
+ .update(data)
49
+ .digest("base64url");
50
+ return `${data}.${signature}`;
51
+ }
52
+ /** Verify and decode a token previously produced by
53
+ * {@link encodeMagicLinkSignupAttribution}. */
54
+ export function decodeMagicLinkSignupAttribution(token, secret, now = Date.now()) {
55
+ if (token === null || token === undefined)
56
+ return undefined;
57
+ if (!token || !secret)
58
+ return null;
59
+ try {
60
+ const dotIndex = token.lastIndexOf(".");
61
+ if (dotIndex <= 0 || dotIndex === token.length - 1)
62
+ return null;
63
+ const data = token.slice(0, dotIndex);
64
+ const signature = token.slice(dotIndex + 1);
65
+ const expected = crypto
66
+ .createHmac("sha256", secret)
67
+ .update(data)
68
+ .digest("base64url");
69
+ if (signature.length !== expected.length ||
70
+ !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
71
+ return null;
72
+ }
73
+ const payload = JSON.parse(Buffer.from(data, "base64url").toString("utf8"));
74
+ if (typeof payload.exp !== "number" ||
75
+ payload.exp < Math.floor(now / 1000)) {
76
+ return null;
77
+ }
78
+ const attribution = sanitizeAttribution(payload.attribution);
79
+ const anonymousId = normalizeAnalyticsAnonymousId(payload.anonymousId);
80
+ if (!attribution && !anonymousId)
81
+ return undefined;
82
+ return {
83
+ ...(attribution ? { attribution } : {}),
84
+ ...(anonymousId ? { anonymousId } : {}),
85
+ };
86
+ }
87
+ catch {
88
+ // coercion-ok: malformed signed input is distinct from absent context.
89
+ return null;
90
+ }
91
+ }
92
+ /**
93
+ * Read the signed handoff from Better Auth's magic-link verification request.
94
+ * Better Auth copies `newUserCallbackURL` into the verification URL, so this
95
+ * remains available even when the link is opened on another device.
96
+ */
97
+ export function readMagicLinkSignupAttribution(requestUrl, secret, now = Date.now()) {
98
+ if (!requestUrl)
99
+ return undefined;
100
+ try {
101
+ const verificationUrl = new URL(requestUrl, "http://localhost");
102
+ if (!verificationUrl.pathname.endsWith("/magic-link/verify")) {
103
+ return undefined;
104
+ }
105
+ const callback = verificationUrl.searchParams.get("newUserCallbackURL");
106
+ if (!callback)
107
+ return undefined;
108
+ const callbackUrl = new URL(callback, verificationUrl.origin);
109
+ return decodeMagicLinkSignupAttribution(callbackUrl.searchParams.get(MAGIC_LINK_ATTRIBUTION_PARAM), secret, now);
110
+ }
111
+ catch {
112
+ // coercion-ok: malformed request input is distinct from absent context.
113
+ return null;
114
+ }
115
+ }
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
26
26
  expiresAt?: undefined;
27
27
  ttlSeconds?: undefined;
28
28
  } | {
29
+ error?: undefined;
29
30
  token: string;
30
31
  expiresAt: string;
31
32
  ttlSeconds: number;
32
- error?: undefined;
33
33
  }>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.153.0",
3
+ "version": "0.153.2",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {