@agent-native/core 0.102.1 → 0.103.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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +15 -0
- package/corpus/core/docs/content/local-file-mode.mdx +99 -489
- package/corpus/core/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
- package/corpus/core/docs/content/locales/ar-SA/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
- package/corpus/core/docs/content/locales/de-DE/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
- package/corpus/core/docs/content/locales/es-ES/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
- package/corpus/core/docs/content/locales/fr-FR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
- package/corpus/core/docs/content/locales/hi-IN/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
- package/corpus/core/docs/content/locales/ja-JP/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
- package/corpus/core/docs/content/locales/ko-KR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
- package/corpus/core/docs/content/locales/pt-BR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
- package/corpus/core/docs/content/locales/zh-CN/template-content.mdx +9 -24
- package/corpus/core/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
- package/corpus/core/docs/content/locales/zh-TW/template-content.mdx +9 -24
- package/corpus/core/docs/content/template-content.mdx +26 -42
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/content-local.ts +86 -18
- package/corpus/core/src/cli/index.ts +2 -2
- package/corpus/core/src/cli/skills-content/content-skill.ts +36 -25
- package/corpus/core/src/cli/skills.ts +47 -13
- package/corpus/core/src/client/AgentPanel.tsx +7 -0
- package/corpus/core/src/client/AssistantChat.tsx +7 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +7 -1
- package/corpus/core/src/client/chat/message-components.tsx +17 -3
- package/corpus/core/src/client/composer/ComposerPlusMenu.tsx +96 -6
- package/corpus/core/src/client/composer/asset-picker-url.ts +80 -0
- package/corpus/core/src/client/error-format.ts +24 -0
- package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +8 -1
- package/corpus/core/src/local-artifacts/index.ts +65 -0
- package/corpus/templates/assets/app/components/library/GenerationPresetsPanel.tsx +325 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +12 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/assets/app/i18n-data.ts +215 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +19 -479
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id_.settings.tsx +446 -0
- package/corpus/templates/assets/app/routes/library.tsx +69 -0
- package/corpus/templates/assets/changelog/2026-07-15-fixed-top-level-image-picker-selections-returning-to-the-com.md +6 -0
- package/corpus/templates/clips/actions/disconnect-calendar.ts +57 -9
- package/corpus/templates/clips/actions/generate-workflow.ts +102 -59
- package/corpus/templates/clips/actions/get-recording-player-data.ts +64 -1
- package/corpus/templates/clips/actions/regenerate-summary.ts +7 -0
- package/corpus/templates/clips/actions/regenerate-title.ts +29 -11
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +34 -13
- package/corpus/templates/clips/app/components/editor/thumbnail-picker.tsx +87 -9
- package/corpus/templates/clips/app/components/editor/trim-handles.tsx +15 -0
- package/corpus/templates/clips/app/components/editor/waveform.tsx +21 -8
- package/corpus/templates/clips/app/components/library/folder-tree.tsx +52 -0
- package/corpus/templates/clips/app/components/player/settings-panel.tsx +116 -58
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +43 -37
- package/corpus/templates/clips/app/components/player/video-player.tsx +50 -2
- package/corpus/templates/clips/app/components/recorder/microphone-visualizer.tsx +22 -3
- package/corpus/templates/clips/app/i18n/ar-SA.ts +5 -2
- package/corpus/templates/clips/app/i18n/de-DE.ts +6 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +5 -2
- package/corpus/templates/clips/app/i18n/es-ES.ts +6 -2
- package/corpus/templates/clips/app/i18n/fr-FR.ts +5 -2
- package/corpus/templates/clips/app/i18n/hi-IN.ts +5 -2
- package/corpus/templates/clips/app/i18n/ja-JP.ts +6 -1
- package/corpus/templates/clips/app/i18n/ko-KR.ts +5 -2
- package/corpus/templates/clips/app/i18n/pt-BR.ts +6 -2
- package/corpus/templates/clips/app/i18n/zh-CN.ts +5 -2
- package/corpus/templates/clips/app/i18n/zh-TW.ts +5 -2
- package/corpus/templates/clips/app/lib/timestamp-mapping.ts +14 -0
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +1 -2
- package/corpus/templates/clips/app/routes/_index.tsx +1 -2
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +98 -32
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +4 -1
- package/corpus/templates/clips/changelog/2026-07-15-ai-workflow-generation-now-stays-visible-avoids-duplicate-re.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-clip-playback-and-cta-controls-remain-interactive-through-ed.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-clip-titles-no-longer-describe-the-product-as-a-loom-alterna.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-description-regeneration-now-opens-the-agent-chat-so-progres.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-direct-recording-urls-now-respect-share-link-and-password-ac.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-disconnecting-a-calendar-now-clears-unrecorded-synced-meetin.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-folders-now-expose-rename-and-delete-actions-directly-in-the.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-recording-pages-now-explain-sign-in-access-and-avoid-showing.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-the-clips-extension-quick-actions-toolbar-can-be-moved-out-o.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-the-editor-now-skips-cut-sections-during-preview-and-keeps-t.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-thumbnail-capture-now-updates-previews-while-scrubbing-and-l.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-15-windows-permission-links-open-the-matching-settings-page.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +117 -2
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +27 -0
- package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +1 -1
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +139 -101
- package/corpus/templates/clips/server/lib/recording-page-access.ts +35 -0
- package/corpus/templates/clips/server/lib/share-password.ts +6 -3
- package/corpus/templates/content/.agents/skills/content/SKILL.md +26 -39
- package/corpus/templates/content/AGENTS.md +25 -30
- package/corpus/templates/content/actions/_builder-docs-client.ts +23 -0
- package/corpus/templates/content/actions/_content-database-source-adapters.ts +21 -3
- package/corpus/templates/content/actions/_content-files.ts +338 -0
- package/corpus/templates/content/actions/_content-space-access.ts +135 -0
- package/corpus/templates/content/actions/_content-spaces.ts +640 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +90 -7
- package/corpus/templates/content/actions/_database-utils.ts +7 -1
- package/corpus/templates/content/actions/_local-file-documents.ts +0 -9
- package/corpus/templates/content/actions/_local-folder-source.ts +53 -0
- package/corpus/templates/content/actions/_property-utils.ts +9 -4
- package/corpus/templates/content/actions/add-database-item.ts +108 -65
- package/corpus/templates/content/actions/backfill-content-files.ts +22 -0
- package/corpus/templates/content/actions/connect-local-folder-source.ts +221 -0
- package/corpus/templates/content/actions/create-content-database.ts +129 -3
- package/corpus/templates/content/actions/create-document.ts +65 -55
- package/corpus/templates/content/actions/create-inline-content-database.ts +6 -1
- package/corpus/templates/content/actions/delete-content-database.ts +3 -0
- package/corpus/templates/content/actions/delete-document.ts +7 -11
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +175 -0
- package/corpus/templates/content/actions/duplicate-database-item.ts +10 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +13 -0
- package/corpus/templates/content/actions/edit-document.ts +2 -28
- package/corpus/templates/content/actions/ensure-content-spaces.ts +26 -0
- package/corpus/templates/content/actions/export-content-source.ts +47 -9
- package/corpus/templates/content/actions/get-document.ts +0 -14
- package/corpus/templates/content/actions/import-content-source.ts +43 -3
- package/corpus/templates/content/actions/list-content-spaces.ts +103 -0
- package/corpus/templates/content/actions/list-documents.ts +12 -9
- package/corpus/templates/content/actions/move-document.ts +3 -14
- package/corpus/templates/content/actions/pull-document.ts +0 -23
- package/corpus/templates/content/actions/remove-local-file-source.ts +50 -29
- package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +442 -0
- package/corpus/templates/content/actions/reveal-local-source-file.ts +6 -17
- package/corpus/templates/content/actions/search-documents.ts +0 -34
- package/corpus/templates/content/actions/share-local-file-document.ts +25 -1
- package/corpus/templates/content/actions/submit-content-database-form.ts +12 -0
- package/corpus/templates/content/actions/sync-local-folder-source.ts +731 -0
- package/corpus/templates/content/actions/sync-manifest-local-folder-source.ts +123 -0
- package/corpus/templates/content/actions/update-content-database-view.ts +10 -1
- package/corpus/templates/content/actions/update-document.ts +0 -15
- package/corpus/templates/content/actions/view-screen.ts +2 -50
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +76 -62
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +70 -22
- package/corpus/templates/content/app/components/editor/database/grouping.ts +2 -1
- package/corpus/templates/content/app/components/editor/database/settings.tsx +23 -20
- package/corpus/templates/content/app/components/editor/database/shared.tsx +2 -0
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +330 -0
- package/corpus/templates/content/app/components/editor/database/types.ts +1 -0
- package/corpus/templates/content/app/components/editor/database/view-config.ts +3 -1
- package/corpus/templates/content/app/components/layout/Layout.tsx +25 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +209 -113
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +31 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +16 -0
- package/corpus/templates/content/app/hooks/use-content-spaces.ts +39 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +11 -0
- package/corpus/templates/content/app/i18n-data.ts +75 -0
- package/corpus/templates/content/app/routes/_app.local-files.tsx +174 -65
- package/corpus/templates/content/changelog/2026-07-16-content-callouts-can-now-be-reviewed-and-published-to-builder.md +6 -0
- package/corpus/templates/content/changelog/2026-07-16-heading-5-and-heading-6-are-available-from-the-editor-slash-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-16-organize-every-workspace-through-a-customizable-files-databa.md +6 -0
- package/corpus/templates/content/parity/matrix.md +2 -1
- package/corpus/templates/content/parity/matrix.ts +37 -1
- package/corpus/templates/content/scripts/dev-database.mjs +7 -5
- package/corpus/templates/content/server/db/schema.ts +113 -40
- package/corpus/templates/content/server/lib/document-context.ts +15 -1
- package/corpus/templates/content/server/lib/notion-sync.ts +6 -0
- package/corpus/templates/content/server/plugins/db.ts +153 -0
- package/corpus/templates/content/shared/api.ts +17 -3
- package/corpus/templates/content/shared/builder-mdx.ts +163 -16
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +59 -0
- package/corpus/templates/design/AGENTS.md +6 -1
- package/corpus/templates/design/actions/create-design-from-template.ts +19 -7
- package/corpus/templates/design/actions/list-design-templates.ts +69 -21
- package/corpus/templates/design/actions/navigate.ts +2 -2
- package/corpus/templates/design/actions/view-screen.ts +15 -2
- package/corpus/templates/design/agent-native.app-skill.json +5 -0
- package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +7 -3
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +281 -56
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +14 -2
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +3 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +3 -3
- package/corpus/templates/design/app/i18n-data.ts +21 -2
- package/corpus/templates/design/app/i18n-template-feature.ts +218 -10
- package/corpus/templates/design/app/pages/Index.tsx +195 -24
- package/corpus/templates/design/app/pages/Templates.tsx +144 -55
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +12 -1
- package/corpus/templates/design/changelog/2026-07-14-templates-now-appear-in-the-new-design-picker-with-previews-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-prototype-print-and-download-buttons-now-work-in-preview.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-templates-now-open-immediately-after-copying-even-while-the-.md +6 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/mail/AGENTS.md +23 -18
- package/corpus/templates/mail/actions/bulk-archive.ts +32 -28
- package/corpus/templates/mail/actions/mark-read.ts +131 -20
- package/corpus/templates/mail/actions/mark-thread-read.ts +1 -1
- package/corpus/templates/mail/actions/move-email.ts +30 -24
- package/corpus/templates/mail/actions/send-email.ts +73 -68
- package/corpus/templates/mail/changelog/2026-07-15-unread-inbox-cleanup-now-completes-reliably.md +6 -0
- package/corpus/templates/mail/server/handlers/emails.ts +218 -209
- package/corpus/templates/mail/server/lib/bulk-mark-read.ts +30 -0
- package/corpus/templates/mail/server/lib/email-state.ts +198 -113
- package/corpus/templates/mail/server/lib/google-auth.ts +218 -25
- package/corpus/templates/mail/server/lib/jobs.ts +69 -74
- package/corpus/templates/mail/server/lib/local-email-store.ts +265 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
- package/corpus/templates/slides/actions/extract-pdf.ts +1 -1
- package/corpus/templates/slides/actions/import-file.ts +4 -1
- package/corpus/templates/slides/app/components/editor/ImageGenPanel.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/ImageSearchPanel.tsx +9 -12
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +29 -21
- package/corpus/templates/slides/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/slides/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/slides/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/slides/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/slides/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/slides/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/slides/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/slides/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/slides/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/slides/changelog/2026-07-15-image-search-and-generation-controls-now-use-the-registered-.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-15-pdf-deck-references-can-be-imported-for-slide-inspiration-ag.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-15-stable-style-inspector-dock.md +6 -0
- package/dist/cli/content-local.d.ts +1 -0
- package/dist/cli/content-local.d.ts.map +1 -1
- package/dist/cli/content-local.js +75 -17
- package/dist/cli/content-local.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/skills-content/content-skill.d.ts +1 -1
- package/dist/cli/skills-content/content-skill.d.ts.map +1 -1
- package/dist/cli/skills-content/content-skill.js +36 -25
- package/dist/cli/skills-content/content-skill.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +47 -14
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +5 -0
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +7 -0
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +8 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +4 -0
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +10 -3
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/composer/ComposerPlusMenu.d.ts.map +1 -1
- package/dist/client/composer/ComposerPlusMenu.js +58 -6
- package/dist/client/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/client/composer/asset-picker-url.d.ts +16 -0
- package/dist/client/composer/asset-picker-url.d.ts.map +1 -0
- package/dist/client/composer/asset-picker-url.js +64 -0
- package/dist/client/composer/asset-picker-url.js.map +1 -0
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +17 -0
- package/dist/client/error-format.js.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +6 -1
- package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/local-artifacts/index.d.ts +10 -0
- package/dist/local-artifacts/index.d.ts.map +1 -1
- package/dist/local-artifacts/index.js +39 -0
- package/dist/local-artifacts/index.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/docs/content/local-file-mode.mdx +99 -489
- package/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
- package/docs/content/locales/ar-SA/template-content.mdx +10 -24
- package/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
- package/docs/content/locales/de-DE/template-content.mdx +10 -24
- package/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
- package/docs/content/locales/es-ES/template-content.mdx +10 -24
- package/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
- package/docs/content/locales/fr-FR/template-content.mdx +10 -24
- package/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
- package/docs/content/locales/hi-IN/template-content.mdx +10 -24
- package/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
- package/docs/content/locales/ja-JP/template-content.mdx +10 -24
- package/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
- package/docs/content/locales/ko-KR/template-content.mdx +10 -24
- package/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
- package/docs/content/locales/pt-BR/template-content.mdx +10 -24
- package/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
- package/docs/content/locales/zh-CN/template-content.mdx +9 -24
- package/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
- package/docs/content/locales/zh-TW/template-content.mdx +9 -24
- package/docs/content/template-content.mdx +26 -42
- package/package.json +1 -1
- package/corpus/templates/assets/app/components/library/EditLibraryDialog.tsx +0 -126
|
@@ -83,6 +83,17 @@
|
|
|
83
83
|
let bubbleDragLayer: HTMLDivElement | null = null;
|
|
84
84
|
let bubblePersistTimer: ReturnType<typeof setTimeout> | undefined;
|
|
85
85
|
|
|
86
|
+
// The quick-actions toolbar is also draggable. Keep its geometry in the
|
|
87
|
+
// content script because the iframe cannot position itself on the host page.
|
|
88
|
+
const TOOLBAR_WIDTH = 68;
|
|
89
|
+
const TOOLBAR_COLLAPSED_HEIGHT = 154;
|
|
90
|
+
const toolbarGeom: { left: number | null; top: number | null } = {
|
|
91
|
+
left: null,
|
|
92
|
+
top: null,
|
|
93
|
+
};
|
|
94
|
+
let toolbarDragLayer: HTMLDivElement | null = null;
|
|
95
|
+
let toolbarPersistTimer: ReturnType<typeof setTimeout> | undefined;
|
|
96
|
+
|
|
86
97
|
function bubbleSizePx(): number {
|
|
87
98
|
return BUBBLE_SIZES[bubbleGeom.size] ?? BUBBLE_SIZES.lg;
|
|
88
99
|
}
|
|
@@ -130,6 +141,92 @@
|
|
|
130
141
|
}, 200);
|
|
131
142
|
}
|
|
132
143
|
|
|
144
|
+
function clampToolbar(
|
|
145
|
+
left: number,
|
|
146
|
+
top: number,
|
|
147
|
+
height: number,
|
|
148
|
+
): { left: number; top: number } {
|
|
149
|
+
return {
|
|
150
|
+
left: Math.max(8, Math.min(left, window.innerWidth - TOOLBAR_WIDTH - 8)),
|
|
151
|
+
top: Math.max(8, Math.min(top, window.innerHeight - height - 8)),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function applyToolbarGeom(): void {
|
|
156
|
+
const frame = document.getElementById(
|
|
157
|
+
partFrameId("toolbar"),
|
|
158
|
+
) as HTMLIFrameElement | null;
|
|
159
|
+
if (!frame) return;
|
|
160
|
+
const height =
|
|
161
|
+
frame.getBoundingClientRect().height || TOOLBAR_COLLAPSED_HEIGHT;
|
|
162
|
+
const base = clampToolbar(
|
|
163
|
+
toolbarGeom.left ?? 16,
|
|
164
|
+
toolbarGeom.top ?? window.innerHeight / 2 - TOOLBAR_COLLAPSED_HEIGHT / 2,
|
|
165
|
+
height,
|
|
166
|
+
);
|
|
167
|
+
Object.assign(frame.style, {
|
|
168
|
+
left: `${base.left}px`,
|
|
169
|
+
top: `${base.top}px`,
|
|
170
|
+
right: "auto",
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function persistToolbarGeom(): void {
|
|
175
|
+
clearTimeout(toolbarPersistTimer);
|
|
176
|
+
toolbarPersistTimer = setTimeout(() => {
|
|
177
|
+
try {
|
|
178
|
+
chrome.storage.local.set({ toolbarGeom });
|
|
179
|
+
} catch {
|
|
180
|
+
/* ignore */
|
|
181
|
+
}
|
|
182
|
+
}, 200);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function startToolbarDrag(): void {
|
|
186
|
+
if (toolbarDragLayer) return;
|
|
187
|
+
const frame = document.getElementById(
|
|
188
|
+
partFrameId("toolbar"),
|
|
189
|
+
) as HTMLIFrameElement | null;
|
|
190
|
+
if (!frame) return;
|
|
191
|
+
|
|
192
|
+
const layer = document.createElement("div");
|
|
193
|
+
Object.assign(layer.style, {
|
|
194
|
+
position: "fixed",
|
|
195
|
+
inset: "0",
|
|
196
|
+
zIndex: "2147483647",
|
|
197
|
+
cursor: "grabbing",
|
|
198
|
+
});
|
|
199
|
+
(document.documentElement || document.body).appendChild(layer);
|
|
200
|
+
toolbarDragLayer = layer;
|
|
201
|
+
|
|
202
|
+
const onMove = (e: PointerEvent): void => {
|
|
203
|
+
const rect = frame.getBoundingClientRect();
|
|
204
|
+
const next = clampToolbar(
|
|
205
|
+
rect.left + e.movementX,
|
|
206
|
+
rect.top + e.movementY,
|
|
207
|
+
rect.height || TOOLBAR_COLLAPSED_HEIGHT,
|
|
208
|
+
);
|
|
209
|
+
toolbarGeom.left = next.left;
|
|
210
|
+
toolbarGeom.top = next.top;
|
|
211
|
+
Object.assign(frame.style, {
|
|
212
|
+
left: `${next.left}px`,
|
|
213
|
+
top: `${next.top}px`,
|
|
214
|
+
right: "auto",
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
const onUp = (): void => {
|
|
218
|
+
layer.removeEventListener("pointermove", onMove);
|
|
219
|
+
layer.removeEventListener("pointerup", onUp);
|
|
220
|
+
layer.removeEventListener("pointercancel", onUp);
|
|
221
|
+
layer.remove();
|
|
222
|
+
toolbarDragLayer = null;
|
|
223
|
+
persistToolbarGeom();
|
|
224
|
+
};
|
|
225
|
+
layer.addEventListener("pointermove", onMove);
|
|
226
|
+
layer.addEventListener("pointerup", onUp);
|
|
227
|
+
layer.addEventListener("pointercancel", onUp);
|
|
228
|
+
}
|
|
229
|
+
|
|
133
230
|
function startBubbleDrag(): void {
|
|
134
231
|
if (bubbleDragLayer) return;
|
|
135
232
|
const frame = document.getElementById(
|
|
@@ -177,7 +274,7 @@
|
|
|
177
274
|
}
|
|
178
275
|
|
|
179
276
|
try {
|
|
180
|
-
chrome.storage.local.get("bubbleGeom", (value) => {
|
|
277
|
+
chrome.storage.local.get(["bubbleGeom", "toolbarGeom"], (value) => {
|
|
181
278
|
if (chrome.runtime.lastError) return;
|
|
182
279
|
const g = value.bubbleGeom as
|
|
183
280
|
| { size?: unknown; left?: unknown; top?: unknown }
|
|
@@ -188,12 +285,24 @@
|
|
|
188
285
|
bubbleGeom.top = typeof g.top === "number" ? g.top : null;
|
|
189
286
|
}
|
|
190
287
|
applyBubbleGeom();
|
|
288
|
+
const toolbar = value.toolbarGeom as
|
|
289
|
+
| { left?: unknown; top?: unknown }
|
|
290
|
+
| undefined;
|
|
291
|
+
if (toolbar && typeof toolbar === "object") {
|
|
292
|
+
toolbarGeom.left =
|
|
293
|
+
typeof toolbar.left === "number" ? toolbar.left : null;
|
|
294
|
+
toolbarGeom.top = typeof toolbar.top === "number" ? toolbar.top : null;
|
|
295
|
+
}
|
|
296
|
+
applyToolbarGeom();
|
|
191
297
|
});
|
|
192
298
|
} catch {
|
|
193
299
|
/* ignore */
|
|
194
300
|
}
|
|
195
301
|
|
|
196
|
-
window.addEventListener("resize", () =>
|
|
302
|
+
window.addEventListener("resize", () => {
|
|
303
|
+
applyBubbleGeom();
|
|
304
|
+
applyToolbarGeom();
|
|
305
|
+
});
|
|
197
306
|
|
|
198
307
|
function readCurrentParts(
|
|
199
308
|
callback: (parts: OverlayPart[] | null) => void,
|
|
@@ -338,6 +447,7 @@
|
|
|
338
447
|
styleFrame(frame, part);
|
|
339
448
|
container.appendChild(frame);
|
|
340
449
|
if (part === "bubble") applyBubbleGeom();
|
|
450
|
+
if (part === "toolbar") applyToolbarGeom();
|
|
341
451
|
}
|
|
342
452
|
|
|
343
453
|
// Camera-ready gating + connecting spinner: while the camera connects we show a
|
|
@@ -520,6 +630,7 @@
|
|
|
520
630
|
const frame = document.getElementById(partFrameId("toolbar"));
|
|
521
631
|
if (frame && typeof data.height === "number") {
|
|
522
632
|
frame.style.height = `${Math.round(data.height)}px`;
|
|
633
|
+
applyToolbarGeom();
|
|
523
634
|
}
|
|
524
635
|
return;
|
|
525
636
|
}
|
|
@@ -537,6 +648,10 @@
|
|
|
537
648
|
startBubbleDrag();
|
|
538
649
|
return;
|
|
539
650
|
}
|
|
651
|
+
if (data.kind === "toolbar-drag-start") {
|
|
652
|
+
startToolbarDrag();
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
540
655
|
if (data.kind === "bubble-size") {
|
|
541
656
|
bubbleGeom.size = data.size === "sm" ? "sm" : "lg";
|
|
542
657
|
applyBubbleGeom();
|
|
@@ -84,6 +84,17 @@ function postToolbarSize(height: number): void {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function postToolbarDragStart(): void {
|
|
88
|
+
try {
|
|
89
|
+
window.parent.postMessage(
|
|
90
|
+
{ source: "clips-overlay", kind: "toolbar-drag-start", part: "toolbar" },
|
|
91
|
+
"*",
|
|
92
|
+
);
|
|
93
|
+
} catch {
|
|
94
|
+
/* parent gone */
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
87
98
|
function postCountdownFinished(): void {
|
|
88
99
|
try {
|
|
89
100
|
window.parent.postMessage(
|
|
@@ -406,6 +417,22 @@ function initCountdown(): void {
|
|
|
406
417
|
function initToolbar(): void {
|
|
407
418
|
const pill = document.createElement("div");
|
|
408
419
|
pill.className = "toolbar-v";
|
|
420
|
+
pill.style.cursor = "grab";
|
|
421
|
+
|
|
422
|
+
// The content script owns iframe geometry, so ask it to capture the pointer
|
|
423
|
+
// page-wide when the user drags the toolbar outside its current bounds.
|
|
424
|
+
pill.addEventListener("pointerdown", (event) => {
|
|
425
|
+
if (event.button !== 0) return;
|
|
426
|
+
if ((event.target as HTMLElement).closest("button")) return;
|
|
427
|
+
event.preventDefault();
|
|
428
|
+
pill.style.cursor = "grabbing";
|
|
429
|
+
postToolbarDragStart();
|
|
430
|
+
const restore = (): void => {
|
|
431
|
+
pill.style.cursor = "grab";
|
|
432
|
+
window.removeEventListener("pointerup", restore);
|
|
433
|
+
};
|
|
434
|
+
window.addEventListener("pointerup", restore);
|
|
435
|
+
});
|
|
409
436
|
|
|
410
437
|
const makeBtn = (
|
|
411
438
|
cls: string,
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"plugins": {
|
|
42
42
|
"shell": {
|
|
43
|
-
"open": "(((mailto:\\w+)|(tel:\\w+)|(https?://\\w+)).+|(zoommtg://([A-Za-z0-9-]+\\.)*zoom\\.us/join\\?.+))"
|
|
43
|
+
"open": "(((mailto:\\w+)|(tel:\\w+)|(https?://\\w+)).+|(zoommtg://([A-Za-z0-9-]+\\.)*zoom\\.us/join\\?.+)|(ms-settings:[A-Za-z0-9._?=&-]+))"
|
|
44
44
|
},
|
|
45
45
|
"updater": {
|
|
46
46
|
"active": true,
|
|
@@ -274,6 +274,27 @@ export function calendarEventToMeetingView(args: {
|
|
|
274
274
|
};
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Acquire the calendar account's write lock inside a caller-owned transaction.
|
|
279
|
+
* Disconnect and materialization both take this lock before touching the
|
|
280
|
+
* account's calendar events, so cleanup cannot race a meeting insert.
|
|
281
|
+
*/
|
|
282
|
+
export async function lockCalendarAccount(
|
|
283
|
+
db: any,
|
|
284
|
+
accountId: string,
|
|
285
|
+
ownerEmail?: string | null,
|
|
286
|
+
): Promise<boolean> {
|
|
287
|
+
const ownerFilter = ownerEmail
|
|
288
|
+
? eq(schema.calendarAccounts.ownerEmail, ownerEmail)
|
|
289
|
+
: isNull(schema.calendarAccounts.ownerEmail);
|
|
290
|
+
const locked = await db
|
|
291
|
+
.update(schema.calendarAccounts)
|
|
292
|
+
.set({ updatedAt: new Date().toISOString() })
|
|
293
|
+
.where(and(eq(schema.calendarAccounts.id, accountId), ownerFilter))
|
|
294
|
+
.returning({ id: schema.calendarAccounts.id });
|
|
295
|
+
return locked.length > 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
277
298
|
export async function fetchLiveCalendarEventFromId(virtualId: string) {
|
|
278
299
|
const parsed = parseCalendarMeetingId(virtualId);
|
|
279
300
|
if (!parsed) return null;
|
|
@@ -317,11 +338,14 @@ export async function fetchLiveCalendarEventFromId(virtualId: string) {
|
|
|
317
338
|
}
|
|
318
339
|
}
|
|
319
340
|
|
|
320
|
-
export async function upsertCalendarEventSnapshot(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
341
|
+
export async function upsertCalendarEventSnapshot(
|
|
342
|
+
args: {
|
|
343
|
+
account: CalendarAccountForEvents;
|
|
344
|
+
event: CalendarEvent;
|
|
345
|
+
},
|
|
346
|
+
database: any = getDb(),
|
|
347
|
+
) {
|
|
348
|
+
const db = database;
|
|
325
349
|
const startIso = eventStartIso(args.event);
|
|
326
350
|
const endIso = eventEndIso(args.event);
|
|
327
351
|
if (!args.event.id || !startIso || !endIso) {
|
|
@@ -397,120 +421,134 @@ export async function materializeCalendarMeetingFromVirtualId(
|
|
|
397
421
|
const live = await fetchLiveCalendarEventFromId(virtualId);
|
|
398
422
|
if (!live) return null;
|
|
399
423
|
const db = getDb();
|
|
400
|
-
const snapshot = await upsertCalendarEventSnapshot(live);
|
|
401
|
-
|
|
402
|
-
if (snapshot.meetingId) {
|
|
403
|
-
const [existingMeeting] = await db
|
|
404
|
-
.select()
|
|
405
|
-
.from(schema.meetings)
|
|
406
|
-
.where(eq(schema.meetings.id, snapshot.meetingId))
|
|
407
|
-
.limit(1);
|
|
408
|
-
if (existingMeeting) return { meeting: existingMeeting, created: false };
|
|
409
|
-
}
|
|
410
|
-
|
|
411
424
|
const ownerEmail = getCurrentOwnerEmail();
|
|
412
425
|
const orgId = (await getActiveOrganizationId().catch(() => null)) ?? null;
|
|
413
426
|
const joinUrl = pickJoinUrl(live.event);
|
|
414
427
|
const meetingId = nanoid();
|
|
415
428
|
const nowIso = new Date().toISOString();
|
|
416
429
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
.update(schema.calendarEvents)
|
|
424
|
-
.set({ meetingId, updatedAt: nowIso })
|
|
425
|
-
.where(
|
|
426
|
-
and(
|
|
427
|
-
eq(schema.calendarEvents.id, snapshot.id),
|
|
428
|
-
isNull(schema.calendarEvents.meetingId),
|
|
429
|
-
),
|
|
430
|
+
return db.transaction(async (tx: any) => {
|
|
431
|
+
// Disconnect takes this same account-row write lock before it snapshots
|
|
432
|
+
// and deletes events. If disconnect wins, the account is gone by the time
|
|
433
|
+
// this transaction reaches the lock and materialization becomes a no-op.
|
|
434
|
+
if (
|
|
435
|
+
!(await lockCalendarAccount(tx, live.account.id, live.account.ownerEmail))
|
|
430
436
|
)
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
.from(schema.calendarEvents)
|
|
438
|
-
.where(eq(schema.calendarEvents.id, snapshot.id))
|
|
439
|
-
.limit(1);
|
|
440
|
-
if (winnerEvent?.meetingId) {
|
|
441
|
-
const [winnerMeeting] = await db
|
|
437
|
+
return null;
|
|
438
|
+
|
|
439
|
+
const snapshot = await upsertCalendarEventSnapshot(live, tx);
|
|
440
|
+
|
|
441
|
+
if (snapshot.meetingId) {
|
|
442
|
+
const [existingMeeting] = await tx
|
|
442
443
|
.select()
|
|
443
444
|
.from(schema.meetings)
|
|
444
|
-
.where(eq(schema.meetings.id,
|
|
445
|
+
.where(eq(schema.meetings.id, snapshot.meetingId))
|
|
445
446
|
.limit(1);
|
|
446
|
-
if (
|
|
447
|
+
if (existingMeeting) {
|
|
448
|
+
return { meeting: existingMeeting, created: false };
|
|
449
|
+
}
|
|
447
450
|
}
|
|
448
|
-
return null;
|
|
449
|
-
}
|
|
450
451
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
scheduledEnd: eventEndIso(live.event),
|
|
458
|
-
actualStart: null,
|
|
459
|
-
actualEnd: null,
|
|
460
|
-
platform: detectPlatform(joinUrl),
|
|
461
|
-
joinUrl: joinUrl ?? null,
|
|
462
|
-
calendarEventId: snapshot.id,
|
|
463
|
-
recordingId: null,
|
|
464
|
-
transcriptStatus: "idle",
|
|
465
|
-
summaryMd: "",
|
|
466
|
-
bulletsJson: "[]",
|
|
467
|
-
actionItemsJson: "[]",
|
|
468
|
-
source: "calendar",
|
|
469
|
-
reminderFiredAt: null,
|
|
470
|
-
createdAt: nowIso,
|
|
471
|
-
updatedAt: nowIso,
|
|
472
|
-
ownerEmail,
|
|
473
|
-
orgId,
|
|
474
|
-
visibility: "private",
|
|
475
|
-
} as any);
|
|
476
|
-
|
|
477
|
-
const participants = calendarEventParticipants(live.event).filter(
|
|
478
|
-
(p) => p.email,
|
|
479
|
-
);
|
|
480
|
-
if (participants.length) {
|
|
481
|
-
await db.insert(schema.meetingParticipants).values(
|
|
482
|
-
participants.map((p) => ({
|
|
483
|
-
id: nanoid(),
|
|
484
|
-
meetingId,
|
|
485
|
-
email: p.email,
|
|
486
|
-
name: p.name ?? null,
|
|
487
|
-
isOrganizer: !!p.isOrganizer,
|
|
488
|
-
attendedAt: null,
|
|
489
|
-
createdAt: nowIso,
|
|
490
|
-
})) as any,
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
} catch (err) {
|
|
494
|
-
// Roll back the claim so a future call can retry — but only if it still
|
|
495
|
-
// points at our own meetingId (don't clobber a legitimate later claim).
|
|
496
|
-
await db
|
|
452
|
+
// Claim the calendar_events row atomically before inserting the meeting
|
|
453
|
+
// row, so two concurrent materialize calls for the same event can't both
|
|
454
|
+
// insert a `meetings` row (check-then-act TOCTOU). Only the caller whose
|
|
455
|
+
// UPDATE actually matched a row (meetingId still NULL) proceeds to insert;
|
|
456
|
+
// the loser re-reads and returns the winner's meeting instead.
|
|
457
|
+
const claimed = await tx
|
|
497
458
|
.update(schema.calendarEvents)
|
|
498
|
-
.set({ meetingId
|
|
459
|
+
.set({ meetingId, updatedAt: nowIso })
|
|
499
460
|
.where(
|
|
500
461
|
and(
|
|
501
462
|
eq(schema.calendarEvents.id, snapshot.id),
|
|
502
|
-
|
|
463
|
+
isNull(schema.calendarEvents.meetingId),
|
|
503
464
|
),
|
|
504
465
|
)
|
|
505
|
-
.
|
|
506
|
-
|
|
507
|
-
|
|
466
|
+
.returning({ id: schema.calendarEvents.id });
|
|
467
|
+
|
|
468
|
+
if (!claimed.length) {
|
|
469
|
+
// Someone else claimed it first — re-read and return the winner.
|
|
470
|
+
const [winnerEvent] = await tx
|
|
471
|
+
.select({ meetingId: schema.calendarEvents.meetingId })
|
|
472
|
+
.from(schema.calendarEvents)
|
|
473
|
+
.where(eq(schema.calendarEvents.id, snapshot.id))
|
|
474
|
+
.limit(1);
|
|
475
|
+
if (winnerEvent?.meetingId) {
|
|
476
|
+
const [winnerMeeting] = await tx
|
|
477
|
+
.select()
|
|
478
|
+
.from(schema.meetings)
|
|
479
|
+
.where(eq(schema.meetings.id, winnerEvent.meetingId))
|
|
480
|
+
.limit(1);
|
|
481
|
+
if (winnerMeeting) {
|
|
482
|
+
return { meeting: winnerMeeting, created: false };
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
508
487
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
488
|
+
try {
|
|
489
|
+
await tx.insert(schema.meetings).values({
|
|
490
|
+
id: meetingId,
|
|
491
|
+
organizationId: orgId ?? live.account.orgId ?? null,
|
|
492
|
+
title: live.event.summary || "Untitled meeting",
|
|
493
|
+
scheduledStart: eventStartIso(live.event),
|
|
494
|
+
scheduledEnd: eventEndIso(live.event),
|
|
495
|
+
actualStart: null,
|
|
496
|
+
actualEnd: null,
|
|
497
|
+
platform: detectPlatform(joinUrl),
|
|
498
|
+
joinUrl: joinUrl ?? null,
|
|
499
|
+
calendarEventId: snapshot.id,
|
|
500
|
+
recordingId: null,
|
|
501
|
+
transcriptStatus: "idle",
|
|
502
|
+
summaryMd: "",
|
|
503
|
+
bulletsJson: "[]",
|
|
504
|
+
actionItemsJson: "[]",
|
|
505
|
+
source: "calendar",
|
|
506
|
+
reminderFiredAt: null,
|
|
507
|
+
createdAt: nowIso,
|
|
508
|
+
updatedAt: nowIso,
|
|
509
|
+
ownerEmail,
|
|
510
|
+
orgId,
|
|
511
|
+
visibility: "private",
|
|
512
|
+
} as any);
|
|
513
|
+
|
|
514
|
+
const participants = calendarEventParticipants(live.event).filter(
|
|
515
|
+
(p) => p.email,
|
|
516
|
+
);
|
|
517
|
+
if (participants.length) {
|
|
518
|
+
await tx.insert(schema.meetingParticipants).values(
|
|
519
|
+
participants.map((p) => ({
|
|
520
|
+
id: nanoid(),
|
|
521
|
+
meetingId,
|
|
522
|
+
email: p.email,
|
|
523
|
+
name: p.name ?? null,
|
|
524
|
+
isOrganizer: !!p.isOrganizer,
|
|
525
|
+
attendedAt: null,
|
|
526
|
+
createdAt: nowIso,
|
|
527
|
+
})) as any,
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
} catch (err) {
|
|
531
|
+
// Roll back the claim so a future call can retry — but only if it still
|
|
532
|
+
// points at our own meetingId (don't clobber a legitimate later claim).
|
|
533
|
+
await tx
|
|
534
|
+
.update(schema.calendarEvents)
|
|
535
|
+
.set({ meetingId: null, updatedAt: new Date().toISOString() })
|
|
536
|
+
.where(
|
|
537
|
+
and(
|
|
538
|
+
eq(schema.calendarEvents.id, snapshot.id),
|
|
539
|
+
eq(schema.calendarEvents.meetingId, meetingId),
|
|
540
|
+
),
|
|
541
|
+
)
|
|
542
|
+
.catch(() => {});
|
|
543
|
+
throw err;
|
|
544
|
+
}
|
|
514
545
|
|
|
515
|
-
|
|
546
|
+
const [meeting] = await tx
|
|
547
|
+
.select()
|
|
548
|
+
.from(schema.meetings)
|
|
549
|
+
.where(eq(schema.meetings.id, meetingId))
|
|
550
|
+
.limit(1);
|
|
551
|
+
|
|
552
|
+
return { meeting, created: true };
|
|
553
|
+
});
|
|
516
554
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type RecordingPageAccessRole = "owner" | "admin" | "editor" | "viewer";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Match the public recording and video routes: only a finite expiry date can
|
|
5
|
+
* expire a recording, and the expiry is strict so the exact instant remains
|
|
6
|
+
* valid until time advances past it.
|
|
7
|
+
*/
|
|
8
|
+
export function isRecordingExpired(
|
|
9
|
+
expiresAt: string | null | undefined,
|
|
10
|
+
now = Date.now(),
|
|
11
|
+
): boolean {
|
|
12
|
+
if (!expiresAt) return false;
|
|
13
|
+
|
|
14
|
+
const expires = new Date(expiresAt).getTime();
|
|
15
|
+
return Number.isFinite(expires) && expires < now;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Decide whether an authenticated request may use the editor/player route.
|
|
20
|
+
* Public visibility is intentionally a share-link concern; a direct `/r/*`
|
|
21
|
+
* request must either be the owner or have an explicit share grant.
|
|
22
|
+
* Password-protected recordings stay on the password-aware share flow for
|
|
23
|
+
* every non-owner so the authenticated action cannot mint a bypass token.
|
|
24
|
+
*/
|
|
25
|
+
export function canOpenDirectRecordingPage(input: {
|
|
26
|
+
role: RecordingPageAccessRole;
|
|
27
|
+
visibility: "private" | "org" | "public";
|
|
28
|
+
hasPassword: boolean;
|
|
29
|
+
hasExplicitShare: boolean;
|
|
30
|
+
}): boolean {
|
|
31
|
+
if (input.role === "owner") return true;
|
|
32
|
+
if (input.hasPassword) return false;
|
|
33
|
+
if (input.visibility === "public") return input.hasExplicitShare;
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
@@ -18,13 +18,16 @@ import {
|
|
|
18
18
|
} from "@agent-native/core/secrets/crypto";
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Encrypt a share password for storage. Empty / nullish
|
|
22
|
-
* null), matching the prior `args.password ?? null`
|
|
21
|
+
* Encrypt a share password for storage. Empty / nullish / whitespace-only
|
|
22
|
+
* input clears it (returns null), matching the prior `args.password ?? null`
|
|
23
|
+
* semantics while also rejecting spaces-only "passwords". Real values are
|
|
24
|
+
* trimmed so accidental leading/trailing whitespace doesn't get baked into
|
|
25
|
+
* the stored password.
|
|
23
26
|
*/
|
|
24
27
|
export function encryptSharePassword(
|
|
25
28
|
raw: string | null | undefined,
|
|
26
29
|
): string | null {
|
|
27
|
-
const value = typeof raw === "string" ? raw : "";
|
|
30
|
+
const value = typeof raw === "string" ? raw.trim() : "";
|
|
28
31
|
if (!value) return null;
|
|
29
32
|
return encryptSecretValue(value);
|
|
30
33
|
}
|