@agent-native/core 0.164.4 → 0.164.12
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 +1 -1
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +84 -0
- package/corpus/templates/calendar/AGENTS.md +5 -0
- package/corpus/templates/calendar/actions/delete-event.ts +1 -1
- package/corpus/templates/calendar/actions/delete-events.ts +508 -0
- package/corpus/templates/calendar/actions/event-action-helpers.ts +1 -1
- package/corpus/templates/calendar/actions/list-events.ts +53 -0
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +36 -47
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +3 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +18 -1
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +256 -38
- package/corpus/templates/calendar/app/hooks/use-google-auth.ts +43 -3
- package/corpus/templates/calendar/app/i18n-data.ts +120 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +113 -0
- package/corpus/templates/calendar/changelog/2026-08-18-the-agent-can-now-remove-many-meetings-at-once-such-as-every.md +6 -0
- package/corpus/templates/calendar/server/handlers/google-auth.ts +119 -3
- package/corpus/templates/calendar/server/lib/event-weekday.ts +112 -0
- package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/calendar/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/calendar/server/routes/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/clips/app/components/player/video-player.tsx +12 -0
- package/corpus/templates/clips/app/routes/_app.dictate.tsx +18 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +114 -20
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +83 -78
- package/corpus/templates/clips/changelog/2026-08-19-clips-nightly-builds-are-available-for-trying-the-latest-upd.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-19-social-preview-video-frames.md +1 -0
- package/corpus/templates/clips/desktop/README.md +19 -4
- package/corpus/templates/clips/desktop/src/app.tsx +78 -16
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -2
- package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +1 -0
- package/corpus/templates/clips/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +41 -3
- package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/clips/server/routes/_agent-native/google/callback.get.ts +22 -1
- package/corpus/templates/clips/server/routes/api/clips-updater.json.get.ts +41 -22
- package/corpus/templates/factory/AGENTS.md +3 -2
- package/corpus/templates/factory/actions/get-factory-graph-version.ts +69 -0
- package/corpus/templates/factory/actions/list-factory-automations.ts +4 -0
- package/corpus/templates/factory/actions/list-factory-graph-versions.ts +82 -0
- package/corpus/templates/factory/actions/restore-factory-graph-version.ts +163 -0
- package/corpus/templates/factory/actions/save-factory-graph.ts +93 -60
- package/corpus/templates/factory/actions/start-builder-for-item.ts +19 -17
- package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +100 -36
- package/corpus/templates/factory/app/components/factory/FactoryHistoryView.tsx +709 -0
- package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +62 -2
- package/corpus/templates/factory/app/components/ui/alert-dialog.tsx +1 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +47 -0
- package/corpus/templates/factory/app/routes/factory.tsx +324 -61
- package/corpus/templates/factory/server/connectors/slack.ts +44 -2
- package/corpus/templates/factory/server/plugins/agent-chat.ts +12 -8
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +13 -0
- package/corpus/templates/factory/server/triage/slack-client.ts +5 -0
- package/corpus/templates/factory/server/triage/slack-poller.ts +72 -4
- package/corpus/templates/mail/actions/view-screen.ts +1 -1
- package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -18
- package/corpus/templates/mail/server/handlers/google-auth.ts +125 -24
- package/corpus/templates/mail/server/lib/gmail-query.ts +5 -2
- package/corpus/templates/mail/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/mail/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/mail/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/mail/server/routes/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +0 -12
- package/corpus/templates/plan/changelog/2026-08-19-plan-prompts-now-guide-you-to-connect-ai-before-sending-and-.md +6 -0
- package/corpus/templates/slides/actions/approve-deck-access-request.ts +234 -0
- package/corpus/templates/slides/actions/get-deck-access-status.ts +35 -2
- package/corpus/templates/slides/actions/request-deck-access.ts +263 -64
- package/corpus/templates/slides/app/components/editor/MissingDeckAccessPane.tsx +196 -0
- package/corpus/templates/slides/app/components/editor/RequestDeckAccessDialog.tsx +101 -0
- package/corpus/templates/slides/app/hooks/use-deck-access.ts +7 -4
- package/corpus/templates/slides/app/i18n/en-US.ts +29 -6
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +147 -169
- package/corpus/templates/slides/app/routes/access-request.approve.tsx +250 -0
- package/corpus/templates/slides/server/db/schema.ts +8 -0
- package/corpus/templates/slides/server/lib/access-request-email.ts +36 -0
- package/corpus/templates/slides/server/lib/emails.ts +26 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +7 -0
- package/corpus/templates/slides/server/plugins/db.ts +50 -0
- package/corpus/templates/slides/shared/deck-access.ts +31 -0
- package/dist/a2a/task-store.d.ts +1 -0
- package/dist/a2a/task-store.js +1 -1
- package/dist/agent/observational-memory/store.d.ts +1 -0
- package/dist/agent/observational-memory/store.js +1 -1
- package/dist/agent/run-store.d.ts +1 -0
- package/dist/agent/run-store.js +1 -1
- package/dist/agent/tool-approval-store.d.ts +1 -0
- package/dist/agent/tool-approval-store.js +1 -1
- package/dist/application-state/store.d.ts +1 -0
- package/dist/application-state/store.js +1 -1
- package/dist/browser-sessions/store.d.ts +1 -0
- package/dist/browser-sessions/store.js +1 -1
- package/dist/checkpoints/store.d.ts +1 -0
- package/dist/checkpoints/store.js +1 -1
- package/dist/cli/code-agent-schedules.d.ts +11 -0
- package/dist/cli/code-agent-schedules.js +44 -5
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.js +2 -2
- package/dist/client/AssistantChat.d.ts +5 -1
- package/dist/client/AssistantChat.js +40 -6
- package/dist/client/EnvironmentBadge.d.ts +7 -13
- package/dist/client/EnvironmentBadge.js +43 -92
- package/dist/client/agent-chat/index.d.ts +1 -0
- package/dist/client/agent-chat/index.js +1 -0
- package/dist/client/agent-chat-adapter.js +76 -50
- package/dist/client/app-providers.js +4 -4
- package/dist/client/chat/message-components.d.ts +4 -0
- package/dist/client/chat/message-components.js +28 -1
- package/dist/client/chat/run-recovery.js +26 -0
- package/dist/client/chat-first/apps-rail.d.ts +1 -0
- package/dist/client/chat-first/apps-rail.js +28 -2
- package/dist/client/sse-event-processor.js +13 -8
- package/dist/client/use-active-agent-chat-run.d.ts +2 -0
- package/dist/client/use-active-agent-chat-run.js +22 -0
- package/dist/coding-tools/sandbox/executions-store.d.ts +1 -0
- package/dist/coding-tools/sandbox/executions-store.js +1 -1
- package/dist/collab/awareness-store.d.ts +1 -0
- package/dist/collab/awareness-store.js +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/storage.d.ts +1 -0
- package/dist/collab/storage.js +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/client.js +3 -3
- package/dist/db/ddl-guard.js +8 -0
- package/dist/db/migration-runtime.d.ts +23 -0
- package/dist/db/migration-runtime.js +41 -0
- package/dist/db/migrations.d.ts +1 -2
- package/dist/db/migrations.js +2 -24
- package/dist/email-catalog/log.d.ts +1 -0
- package/dist/email-catalog/log.js +1 -1
- package/dist/guards/index.d.ts +1 -0
- package/dist/guards/index.js +1 -0
- package/dist/guards/release-schema-complete.d.ts +22 -0
- package/dist/guards/release-schema-complete.js +144 -0
- package/dist/integrations/a2a-continuations-store.d.ts +1 -0
- package/dist/integrations/a2a-continuations-store.js +1 -1
- package/dist/integrations/awaiting-input-store.d.ts +1 -0
- package/dist/integrations/awaiting-input-store.js +1 -1
- package/dist/integrations/config-store.d.ts +1 -0
- package/dist/integrations/config-store.js +1 -1
- package/dist/integrations/controls-store.d.ts +1 -0
- package/dist/integrations/controls-store.js +1 -1
- package/dist/integrations/identity-links-store.d.ts +1 -0
- package/dist/integrations/identity-links-store.js +1 -1
- package/dist/integrations/installations-store.d.ts +1 -0
- package/dist/integrations/installations-store.js +1 -1
- package/dist/integrations/remote-commands-store.d.ts +1 -0
- package/dist/integrations/remote-commands-store.js +1 -1
- package/dist/integrations/remote-devices-store.d.ts +1 -0
- package/dist/integrations/remote-devices-store.js +1 -1
- package/dist/integrations/remote-push-store.d.ts +1 -0
- package/dist/integrations/remote-push-store.js +1 -1
- package/dist/integrations/remote-run-events-store.d.ts +1 -0
- package/dist/integrations/remote-run-events-store.js +1 -1
- package/dist/integrations/scope-store.d.ts +1 -0
- package/dist/integrations/scope-store.js +1 -1
- package/dist/integrations/thread-mapping-store.d.ts +1 -0
- package/dist/integrations/thread-mapping-store.js +1 -1
- package/dist/integrations/usage-budget-store.d.ts +1 -0
- package/dist/integrations/usage-budget-store.js +1 -1
- package/dist/jobs/background-automation-runner.js +111 -9
- package/dist/jobs/run-history.d.ts +1 -0
- package/dist/jobs/run-history.js +1 -1
- package/dist/jobs/scheduler-health.d.ts +1 -0
- package/dist/jobs/scheduler-health.js +1 -1
- package/dist/mcp/approval-store.d.ts +1 -0
- package/dist/mcp/approval-store.js +1 -1
- package/dist/mcp/connect-store.d.ts +1 -0
- package/dist/mcp/connect-store.js +1 -1
- package/dist/mcp/oauth-store.d.ts +1 -0
- package/dist/mcp/oauth-store.js +1 -1
- package/dist/notifications/store.d.ts +1 -0
- package/dist/notifications/store.js +1 -1
- package/dist/oauth-tokens/store.d.ts +1 -0
- package/dist/oauth-tokens/store.js +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/progress/store.d.ts +1 -0
- package/dist/progress/store.js +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/provider-api/corpus-jobs-store.d.ts +1 -0
- package/dist/provider-api/corpus-jobs-store.js +1 -1
- package/dist/provider-api/custom-registry.d.ts +1 -0
- package/dist/provider-api/custom-registry.js +1 -1
- package/dist/provider-api/quota-governor.d.ts +1 -0
- package/dist/provider-api/quota-governor.js +1 -1
- package/dist/provider-api/staged-datasets-store.d.ts +1 -0
- package/dist/provider-api/staged-datasets-store.js +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/resources/store.d.ts +1 -0
- package/dist/resources/store.js +1 -1
- package/dist/scripts/dev/index.js +15 -1
- package/dist/search/index.js +3 -0
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/secrets/storage.d.ts +1 -0
- package/dist/secrets/storage.js +1 -1
- package/dist/server/agent-chat-plugin.js +38 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-teams-run-queue.d.ts +1 -0
- package/dist/server/agent-teams-run-queue.js +1 -1
- package/dist/server/auth-marketing.js +3 -1
- package/dist/server/auth.d.ts +18 -1
- package/dist/server/auth.js +370 -154
- package/dist/server/embed-session.d.ts +1 -0
- package/dist/server/embed-session.js +1 -1
- package/dist/server/google-auth-plugin.js +3 -1
- package/dist/server/google-oauth.d.ts +10 -0
- package/dist/server/google-oauth.js +25 -1
- package/dist/server/identity-sso-store.d.ts +1 -0
- package/dist/server/identity-sso-store.js +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/onboarding-html.js +200 -31
- package/dist/server/poll.d.ts +2 -0
- package/dist/server/poll.js +19 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/release-migrations.js +5 -0
- package/dist/server/release-schema.d.ts +46 -0
- package/dist/server/release-schema.js +307 -0
- package/dist/settings/store.d.ts +1 -0
- package/dist/settings/store.js +1 -1
- package/dist/shared/environment-lanes.d.ts +28 -0
- package/dist/shared/environment-lanes.js +61 -0
- package/dist/sharing/actions/share-resource.js +22 -2
- package/dist/styles/agent-native.css +17 -15
- package/dist/triggers/routes.d.ts +11 -0
- package/dist/triggers/routes.js +10 -0
- package/dist/usage/alerts-store.d.ts +1 -0
- package/dist/usage/alerts-store.js +1 -1
- package/dist/usage/store.d.ts +1 -0
- package/dist/usage/store.js +1 -1
- package/docs/content/actions-access-control.mdx +136 -2
- package/docs/content/actions-agent-tools.mdx +101 -0
- package/docs/content/actions-defining.mdx +2 -2
- package/docs/content/audit-log.mdx +1 -1
- package/docs/content/authentication.mdx +1 -1
- package/docs/content/cloudflare.mdx +43 -0
- package/docs/content/creating-templates.mdx +1 -1
- package/docs/content/deploy-an-app.mdx +109 -0
- package/docs/content/deployment-environment-variables.mdx +101 -0
- package/docs/content/deployment.mdx +125 -792
- package/docs/content/docs-components.mdx +2 -2
- package/docs/content/environment-variables.mdx +41 -41
- package/docs/content/file-uploads.mdx +74 -40
- package/docs/content/harness-agents.mdx +2 -2
- package/docs/content/internationalization.mdx +61 -65
- package/docs/content/key-concepts.mdx +3 -3
- package/docs/content/multi-app-workspace.mdx +2 -2
- package/docs/content/netlify.mdx +47 -0
- package/docs/content/node-docker.mdx +138 -0
- package/docs/content/organizations-teams-permissions.mdx +5 -5
- package/docs/content/other-platforms.mdx +27 -0
- package/docs/content/ssr-caching.mdx +45 -0
- package/docs/content/template-assets-developers.mdx +2 -2
- package/docs/content/template-chat-developers.mdx +2 -2
- package/docs/content/template-content-developers.mdx +2 -2
- package/docs/content/{local-file-mode.mdx → template-content-local-files.mdx} +2 -1
- package/docs/content/template-content-sync.mdx +2 -2
- package/docs/content/toolkit-agent-ux.mdx +1 -1
- package/docs/content/toolkit-resources.mdx +2 -2
- package/docs/content/updating-ui-in-production.mdx +35 -0
- package/docs/content/vercel.mdx +38 -0
- package/docs/content/what-is-agent-native.mdx +1 -1
- package/docs/content/workspace-deployment.mdx +350 -0
- package/package.json +2 -2
- package/docs/content/agents.mdx +0 -9
- package/docs/content/database.mdx +0 -317
- package/docs/content/human-approval.mdx +0 -156
|
@@ -45,6 +45,25 @@ const DESKTOP_AUTH_POLL_ABORT_MS = Math.max(
|
|
|
45
45
|
DESKTOP_AUTH_POLL_INTERVAL_MS * 4,
|
|
46
46
|
);
|
|
47
47
|
|
|
48
|
+
function newDesktopOAuthVerifier(): string | null {
|
|
49
|
+
const cryptoApi = globalThis.crypto;
|
|
50
|
+
const randomUuid = cryptoApi?.randomUUID;
|
|
51
|
+
if (typeof randomUuid === "function") {
|
|
52
|
+
return `${randomUuid.call(cryptoApi)}${randomUuid.call(cryptoApi)}`;
|
|
53
|
+
}
|
|
54
|
+
if (typeof cryptoApi?.getRandomValues === "function") {
|
|
55
|
+
const bytes = new Uint8Array(32);
|
|
56
|
+
cryptoApi.getRandomValues(bytes);
|
|
57
|
+
let binary = "";
|
|
58
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
59
|
+
return btoa(binary)
|
|
60
|
+
.replace(/\+/g, "-")
|
|
61
|
+
.replace(/\//g, "_")
|
|
62
|
+
.replace(/=+$/, "");
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
function bodyError(
|
|
49
68
|
body: any,
|
|
50
69
|
raw: string | undefined,
|
|
@@ -229,6 +248,15 @@ export function useGoogleDesktopAuth(options: DesktopAuthOptions = {}) {
|
|
|
229
248
|
const flowId =
|
|
230
249
|
globalThis.crypto?.randomUUID?.() ||
|
|
231
250
|
Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
251
|
+
const verifier = newDesktopOAuthVerifier();
|
|
252
|
+
if (!verifier) {
|
|
253
|
+
setIsPending(false);
|
|
254
|
+
onError?.({
|
|
255
|
+
code: "desktop_auth_start_failed",
|
|
256
|
+
message: "Secure OAuth verifier generation is unavailable.",
|
|
257
|
+
});
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
232
260
|
const redirectUri = oauthRedirectUri("/_agent-native/google/callback");
|
|
233
261
|
const params = new URLSearchParams({
|
|
234
262
|
redirect_uri: redirectUri,
|
|
@@ -285,7 +313,13 @@ export function useGoogleDesktopAuth(options: DesktopAuthOptions = {}) {
|
|
|
285
313
|
: "/_agent-native/google/auth-url";
|
|
286
314
|
const { url } = await fetchJson<{ url: string }>(
|
|
287
315
|
agentNativePath(`${path}?${params.toString()}`),
|
|
288
|
-
{
|
|
316
|
+
{
|
|
317
|
+
method: "POST",
|
|
318
|
+
credentials: "include",
|
|
319
|
+
headers: {
|
|
320
|
+
"X-Agent-Native-Desktop-Verifier": verifier,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
289
323
|
);
|
|
290
324
|
openAuthUrl(url);
|
|
291
325
|
} catch (err) {
|
|
@@ -305,9 +339,15 @@ export function useGoogleDesktopAuth(options: DesktopAuthOptions = {}) {
|
|
|
305
339
|
try {
|
|
306
340
|
const exchangeRes = await fetch(
|
|
307
341
|
agentNativePath(
|
|
308
|
-
`/_agent-native/auth/desktop-exchange?flow_id=${flowId}`,
|
|
342
|
+
`/_agent-native/auth/desktop-exchange?flow_id=${encodeURIComponent(flowId)}`,
|
|
309
343
|
),
|
|
310
|
-
{
|
|
344
|
+
{
|
|
345
|
+
credentials: "include",
|
|
346
|
+
headers: {
|
|
347
|
+
"X-Agent-Native-Desktop-Verifier": verifier,
|
|
348
|
+
},
|
|
349
|
+
signal: controller.signal,
|
|
350
|
+
},
|
|
311
351
|
);
|
|
312
352
|
const exchange = await exchangeRes.json();
|
|
313
353
|
if (exchange?.error) {
|
|
@@ -748,8 +748,10 @@ const enUS = {
|
|
|
748
748
|
customEllipsis: "Custom...",
|
|
749
749
|
customRepeatGoogleCalendar:
|
|
750
750
|
"Custom repeat schedules must be edited in Google Calendar.",
|
|
751
|
+
customRecurrenceTitle: "Custom recurrence",
|
|
751
752
|
customSchedule: "Custom schedule",
|
|
752
753
|
daily: "Daily",
|
|
754
|
+
day: "day",
|
|
753
755
|
default: "Default",
|
|
754
756
|
delete: "Delete",
|
|
755
757
|
description: "Description",
|
|
@@ -800,6 +802,7 @@ const enUS = {
|
|
|
800
802
|
location: "Location",
|
|
801
803
|
meet: "Meet",
|
|
802
804
|
monthly: "Monthly",
|
|
805
|
+
month: "month",
|
|
803
806
|
newEvent: "New Event",
|
|
804
807
|
newEventLower: "New event",
|
|
805
808
|
noGuests: "No guests",
|
|
@@ -832,6 +835,13 @@ const enUS = {
|
|
|
832
835
|
recurringResponseScope: "Recurring response scope",
|
|
833
836
|
repeats: "Repeats",
|
|
834
837
|
repeat: "Repeat",
|
|
838
|
+
repeatEvery: "Repeat every",
|
|
839
|
+
repeatOn: "Repeat on",
|
|
840
|
+
ends: "Ends",
|
|
841
|
+
never: "Never",
|
|
842
|
+
on: "On",
|
|
843
|
+
after: "After",
|
|
844
|
+
occurrences: "occurrences",
|
|
835
845
|
reviewInvite: "Review Invite",
|
|
836
846
|
responseAwaitingCount: "{{count}} awaiting",
|
|
837
847
|
responseMaybeCount: "{{count}} maybe",
|
|
@@ -888,12 +898,14 @@ const enUS = {
|
|
|
888
898
|
onMonthDay: "on the {{day}}",
|
|
889
899
|
weekdaysShort: "Mon–Fri",
|
|
890
900
|
weekly: "Weekly",
|
|
901
|
+
week: "week",
|
|
891
902
|
workingFrom: "Working from",
|
|
892
903
|
workingLocation: "Working location",
|
|
893
904
|
workingLocationDesk: "Desk {{desk}}",
|
|
894
905
|
workingLocationFloor: "Floor {{floor}}",
|
|
895
906
|
workingLocationTitle: "Working location: {{location}}",
|
|
896
907
|
yearly: "Yearly",
|
|
908
|
+
year: "year",
|
|
897
909
|
zoom: "Zoom",
|
|
898
910
|
zoomAdded: "Zoom added",
|
|
899
911
|
zoomAddFailed: "Failed to add Zoom",
|
|
@@ -6141,6 +6153,18 @@ const translatedCalendarInlineEditorLabels = {
|
|
|
6141
6153
|
"zh-CN": {
|
|
6142
6154
|
eventForm: {
|
|
6143
6155
|
addGuest: "添加客人",
|
|
6156
|
+
customRecurrenceTitle: "自定义重复",
|
|
6157
|
+
day: "天",
|
|
6158
|
+
month: "月",
|
|
6159
|
+
repeatEvery: "每隔",
|
|
6160
|
+
repeatOn: "重复于",
|
|
6161
|
+
ends: "结束",
|
|
6162
|
+
never: "从不",
|
|
6163
|
+
on: "于",
|
|
6164
|
+
after: "之后",
|
|
6165
|
+
occurrences: "次",
|
|
6166
|
+
week: "周",
|
|
6167
|
+
year: "年",
|
|
6144
6168
|
everyTwoWeeks: "每两周",
|
|
6145
6169
|
repeat: "重复",
|
|
6146
6170
|
onDate: "于 {{date}}",
|
|
@@ -6152,6 +6176,18 @@ const translatedCalendarInlineEditorLabels = {
|
|
|
6152
6176
|
"es-ES": {
|
|
6153
6177
|
eventForm: {
|
|
6154
6178
|
addGuest: "Añadir invitado",
|
|
6179
|
+
customRecurrenceTitle: "Repetición personalizada",
|
|
6180
|
+
day: "día",
|
|
6181
|
+
month: "mes",
|
|
6182
|
+
repeatEvery: "Repetir cada",
|
|
6183
|
+
repeatOn: "Repetir el",
|
|
6184
|
+
ends: "Termina",
|
|
6185
|
+
never: "Nunca",
|
|
6186
|
+
on: "El",
|
|
6187
|
+
after: "Después de",
|
|
6188
|
+
occurrences: "repeticiones",
|
|
6189
|
+
week: "semana",
|
|
6190
|
+
year: "año",
|
|
6155
6191
|
everyTwoWeeks: "Cada 2 semanas",
|
|
6156
6192
|
repeat: "Repetir",
|
|
6157
6193
|
onDate: "el {{date}}",
|
|
@@ -6163,6 +6199,18 @@ const translatedCalendarInlineEditorLabels = {
|
|
|
6163
6199
|
"fr-FR": {
|
|
6164
6200
|
eventForm: {
|
|
6165
6201
|
addGuest: "Ajouter un invité",
|
|
6202
|
+
customRecurrenceTitle: "Répétition personnalisée",
|
|
6203
|
+
day: "jour",
|
|
6204
|
+
month: "mois",
|
|
6205
|
+
repeatEvery: "Répéter tous les",
|
|
6206
|
+
repeatOn: "Répéter le",
|
|
6207
|
+
ends: "Se termine",
|
|
6208
|
+
never: "Jamais",
|
|
6209
|
+
on: "Le",
|
|
6210
|
+
after: "Après",
|
|
6211
|
+
occurrences: "occurrences",
|
|
6212
|
+
week: "semaine",
|
|
6213
|
+
year: "an",
|
|
6166
6214
|
everyTwoWeeks: "Toutes les 2 semaines",
|
|
6167
6215
|
repeat: "Répéter",
|
|
6168
6216
|
onDate: "le {{date}}",
|
|
@@ -6174,6 +6222,18 @@ const translatedCalendarInlineEditorLabels = {
|
|
|
6174
6222
|
"de-DE": {
|
|
6175
6223
|
eventForm: {
|
|
6176
6224
|
addGuest: "Gast hinzufügen",
|
|
6225
|
+
customRecurrenceTitle: "Benutzerdefinierte Wiederholung",
|
|
6226
|
+
day: "Tag",
|
|
6227
|
+
month: "Monat",
|
|
6228
|
+
repeatEvery: "Wiederholen alle",
|
|
6229
|
+
repeatOn: "Wiederholen am",
|
|
6230
|
+
ends: "Endet",
|
|
6231
|
+
never: "Nie",
|
|
6232
|
+
on: "Am",
|
|
6233
|
+
after: "Nach",
|
|
6234
|
+
occurrences: "Vorkommen",
|
|
6235
|
+
week: "Woche",
|
|
6236
|
+
year: "Jahr",
|
|
6177
6237
|
everyTwoWeeks: "Alle 2 Wochen",
|
|
6178
6238
|
repeat: "Wiederholen",
|
|
6179
6239
|
onDate: "am {{date}}",
|
|
@@ -6185,6 +6245,18 @@ const translatedCalendarInlineEditorLabels = {
|
|
|
6185
6245
|
"ja-JP": {
|
|
6186
6246
|
eventForm: {
|
|
6187
6247
|
addGuest: "ゲストを追加",
|
|
6248
|
+
customRecurrenceTitle: "カスタム繰り返し",
|
|
6249
|
+
day: "日",
|
|
6250
|
+
month: "月",
|
|
6251
|
+
repeatEvery: "繰り返す間隔",
|
|
6252
|
+
repeatOn: "繰り返す曜日",
|
|
6253
|
+
ends: "終了",
|
|
6254
|
+
never: "なし",
|
|
6255
|
+
on: "指定日",
|
|
6256
|
+
after: "次の",
|
|
6257
|
+
occurrences: "回",
|
|
6258
|
+
week: "週",
|
|
6259
|
+
year: "年",
|
|
6188
6260
|
everyTwoWeeks: "2週間ごと",
|
|
6189
6261
|
repeat: "繰り返し",
|
|
6190
6262
|
onDate: "{{date}}",
|
|
@@ -6196,6 +6268,18 @@ const translatedCalendarInlineEditorLabels = {
|
|
|
6196
6268
|
"ko-KR": {
|
|
6197
6269
|
eventForm: {
|
|
6198
6270
|
addGuest: "게스트 추가",
|
|
6271
|
+
customRecurrenceTitle: "맞춤 반복",
|
|
6272
|
+
day: "일",
|
|
6273
|
+
month: "월",
|
|
6274
|
+
repeatEvery: "반복 간격",
|
|
6275
|
+
repeatOn: "반복 요일",
|
|
6276
|
+
ends: "종료",
|
|
6277
|
+
never: "안 함",
|
|
6278
|
+
on: "날짜",
|
|
6279
|
+
after: "후",
|
|
6280
|
+
occurrences: "회",
|
|
6281
|
+
week: "주",
|
|
6282
|
+
year: "년",
|
|
6199
6283
|
everyTwoWeeks: "2주마다",
|
|
6200
6284
|
repeat: "반복",
|
|
6201
6285
|
onDate: "{{date}}",
|
|
@@ -6207,6 +6291,18 @@ const translatedCalendarInlineEditorLabels = {
|
|
|
6207
6291
|
"pt-BR": {
|
|
6208
6292
|
eventForm: {
|
|
6209
6293
|
addGuest: "Adicionar convidado",
|
|
6294
|
+
customRecurrenceTitle: "Repetição personalizada",
|
|
6295
|
+
day: "dia",
|
|
6296
|
+
month: "mês",
|
|
6297
|
+
repeatEvery: "Repetir a cada",
|
|
6298
|
+
repeatOn: "Repetir em",
|
|
6299
|
+
ends: "Termina",
|
|
6300
|
+
never: "Nunca",
|
|
6301
|
+
on: "Em",
|
|
6302
|
+
after: "Após",
|
|
6303
|
+
occurrences: "ocorrências",
|
|
6304
|
+
week: "semana",
|
|
6305
|
+
year: "ano",
|
|
6210
6306
|
everyTwoWeeks: "A cada 2 semanas",
|
|
6211
6307
|
repeat: "Repetir",
|
|
6212
6308
|
onDate: "em {{date}}",
|
|
@@ -6218,6 +6314,18 @@ const translatedCalendarInlineEditorLabels = {
|
|
|
6218
6314
|
"hi-IN": {
|
|
6219
6315
|
eventForm: {
|
|
6220
6316
|
addGuest: "अतिथि जोड़ें",
|
|
6317
|
+
customRecurrenceTitle: "कस्टम दोहराव",
|
|
6318
|
+
day: "दिन",
|
|
6319
|
+
month: "महीना",
|
|
6320
|
+
repeatEvery: "हर",
|
|
6321
|
+
repeatOn: "इन दिनों दोहराएं",
|
|
6322
|
+
ends: "समाप्त",
|
|
6323
|
+
never: "कभी नहीं",
|
|
6324
|
+
on: "इस दिन",
|
|
6325
|
+
after: "के बाद",
|
|
6326
|
+
occurrences: "बार",
|
|
6327
|
+
week: "सप्ताह",
|
|
6328
|
+
year: "वर्ष",
|
|
6221
6329
|
everyTwoWeeks: "हर 2 सप्ताह",
|
|
6222
6330
|
repeat: "दोहराएं",
|
|
6223
6331
|
onDate: "{{date}} को",
|
|
@@ -6229,6 +6337,18 @@ const translatedCalendarInlineEditorLabels = {
|
|
|
6229
6337
|
"ar-SA": {
|
|
6230
6338
|
eventForm: {
|
|
6231
6339
|
addGuest: "إضافة ضيف",
|
|
6340
|
+
customRecurrenceTitle: "تكرار مخصص",
|
|
6341
|
+
day: "يوم",
|
|
6342
|
+
month: "شهر",
|
|
6343
|
+
repeatEvery: "تكرار كل",
|
|
6344
|
+
repeatOn: "تكرار في",
|
|
6345
|
+
ends: "ينتهي",
|
|
6346
|
+
never: "أبدًا",
|
|
6347
|
+
on: "في",
|
|
6348
|
+
after: "بعد",
|
|
6349
|
+
occurrences: "مرات",
|
|
6350
|
+
week: "أسبوع",
|
|
6351
|
+
year: "سنة",
|
|
6232
6352
|
everyTwoWeeks: "كل أسبوعين",
|
|
6233
6353
|
repeat: "تكرار",
|
|
6234
6354
|
onDate: "في {{date}}",
|
|
@@ -12,6 +12,18 @@ export type RecurrencePreset =
|
|
|
12
12
|
| "yearly"
|
|
13
13
|
| "custom";
|
|
14
14
|
|
|
15
|
+
export type RecurrenceUnit = "day" | "week" | "month" | "year";
|
|
16
|
+
export type RecurrenceEndMode = "never" | "date" | "count";
|
|
17
|
+
|
|
18
|
+
export interface CustomRecurrenceDraft {
|
|
19
|
+
interval: number;
|
|
20
|
+
unit: RecurrenceUnit;
|
|
21
|
+
days: string[];
|
|
22
|
+
endMode: RecurrenceEndMode;
|
|
23
|
+
endDate: string;
|
|
24
|
+
count: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
15
27
|
export interface ReminderDraft {
|
|
16
28
|
id: string;
|
|
17
29
|
method: ReminderMethod;
|
|
@@ -387,6 +399,107 @@ export function getRecurrencePreset(recurrence?: string[]): RecurrencePreset {
|
|
|
387
399
|
return "custom";
|
|
388
400
|
}
|
|
389
401
|
|
|
402
|
+
function recurrenceFieldValue(rule: string, key: string): string | undefined {
|
|
403
|
+
return rule.match(new RegExp(`${key}=([^;]+)`, "i"))?.[1];
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function recurrenceWeekday(startIso: string, timeZone?: string) {
|
|
407
|
+
return eventWeekdayCode(startIso, timeZone);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export function createCustomRecurrenceDraft(
|
|
411
|
+
startIso: string,
|
|
412
|
+
timeZone?: string,
|
|
413
|
+
): CustomRecurrenceDraft {
|
|
414
|
+
return {
|
|
415
|
+
interval: 1,
|
|
416
|
+
unit: "week",
|
|
417
|
+
days: [recurrenceWeekday(startIso, timeZone)],
|
|
418
|
+
endMode: "never",
|
|
419
|
+
endDate: "",
|
|
420
|
+
count: 13,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function parseCustomRecurrence(
|
|
425
|
+
recurrence: string[] | undefined,
|
|
426
|
+
startIso: string,
|
|
427
|
+
timeZone?: string,
|
|
428
|
+
): CustomRecurrenceDraft {
|
|
429
|
+
const draft = createCustomRecurrenceDraft(startIso, timeZone);
|
|
430
|
+
const rule = recurrenceRule(recurrence);
|
|
431
|
+
if (!rule) return draft;
|
|
432
|
+
|
|
433
|
+
const freq = recurrenceFieldValue(rule, "FREQ")?.toLowerCase();
|
|
434
|
+
const unit: RecurrenceUnit =
|
|
435
|
+
freq === "daily"
|
|
436
|
+
? "day"
|
|
437
|
+
: freq === "weekly"
|
|
438
|
+
? "week"
|
|
439
|
+
: freq === "monthly"
|
|
440
|
+
? "month"
|
|
441
|
+
: freq === "yearly"
|
|
442
|
+
? "year"
|
|
443
|
+
: draft.unit;
|
|
444
|
+
const interval = Number.parseInt(
|
|
445
|
+
recurrenceFieldValue(rule, "INTERVAL") || "1",
|
|
446
|
+
10,
|
|
447
|
+
);
|
|
448
|
+
const count = Number.parseInt(
|
|
449
|
+
recurrenceFieldValue(rule, "COUNT") || String(draft.count),
|
|
450
|
+
10,
|
|
451
|
+
);
|
|
452
|
+
const until = recurrenceFieldValue(rule, "UNTIL");
|
|
453
|
+
const byDay = recurrenceFieldValue(rule, "BYDAY")
|
|
454
|
+
?.split(",")
|
|
455
|
+
.map((day) => day.replace(/^[+-]?\d+/, "").toUpperCase())
|
|
456
|
+
.filter(Boolean);
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
interval: Number.isFinite(interval) ? Math.max(1, interval) : 1,
|
|
460
|
+
unit,
|
|
461
|
+
days: byDay && byDay.length > 0 ? byDay : unit === "week" ? draft.days : [],
|
|
462
|
+
endMode: until
|
|
463
|
+
? "date"
|
|
464
|
+
: recurrenceFieldValue(rule, "COUNT")
|
|
465
|
+
? "count"
|
|
466
|
+
: "never",
|
|
467
|
+
endDate:
|
|
468
|
+
until && /^\d{8}/.test(until)
|
|
469
|
+
? `${until.slice(0, 4)}-${until.slice(4, 6)}-${until.slice(6, 8)}`
|
|
470
|
+
: "",
|
|
471
|
+
count: Number.isFinite(count) ? Math.max(1, count) : draft.count,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function buildCustomRecurrenceRules(
|
|
476
|
+
draft: CustomRecurrenceDraft,
|
|
477
|
+
): string[] {
|
|
478
|
+
const interval = Math.max(1, Math.round(draft.interval));
|
|
479
|
+
const frequency = {
|
|
480
|
+
day: "DAILY",
|
|
481
|
+
week: "WEEKLY",
|
|
482
|
+
month: "MONTHLY",
|
|
483
|
+
year: "YEARLY",
|
|
484
|
+
}[draft.unit];
|
|
485
|
+
const parts = [
|
|
486
|
+
`FREQ=${frequency}`,
|
|
487
|
+
...(interval > 1 ? [`INTERVAL=${interval}`] : []),
|
|
488
|
+
];
|
|
489
|
+
if (draft.unit === "week" && draft.days.length > 0) {
|
|
490
|
+
parts.push(`BYDAY=${draft.days.join(",")}`);
|
|
491
|
+
}
|
|
492
|
+
if (draft.unit === "month" && draft.days.length > 0) {
|
|
493
|
+
parts.push(`BYDAY=${draft.days.join(",")}`);
|
|
494
|
+
}
|
|
495
|
+
if (draft.endMode === "date" && /^\d{4}-\d{2}-\d{2}$/.test(draft.endDate)) {
|
|
496
|
+
parts.push(`UNTIL=${draft.endDate.replace(/-/g, "")}T235959Z`);
|
|
497
|
+
} else if (draft.endMode === "count") {
|
|
498
|
+
parts.push(`COUNT=${Math.max(1, Math.round(draft.count))}`);
|
|
499
|
+
}
|
|
500
|
+
return [`RRULE:${parts.join(";")}`];
|
|
501
|
+
}
|
|
502
|
+
|
|
390
503
|
export function buildRecurrenceRules(
|
|
391
504
|
preset: RecurrencePreset,
|
|
392
505
|
startIso: string,
|
|
@@ -16,6 +16,9 @@ import {
|
|
|
16
16
|
oauthCallbackResponse,
|
|
17
17
|
oauthDesktopExchangePage,
|
|
18
18
|
oauthErrorPage,
|
|
19
|
+
registerDesktopExchange,
|
|
20
|
+
prepareDesktopOAuthBrowserBinding,
|
|
21
|
+
matchesDesktopOAuthBrowserBinding,
|
|
19
22
|
setDesktopExchange,
|
|
20
23
|
setDesktopExchangeError,
|
|
21
24
|
safeReturnPath,
|
|
@@ -23,6 +26,8 @@ import {
|
|
|
23
26
|
} from "@agent-native/core/server";
|
|
24
27
|
import {
|
|
25
28
|
defineEventHandler,
|
|
29
|
+
getHeader,
|
|
30
|
+
getMethod,
|
|
26
31
|
getQuery,
|
|
27
32
|
setResponseStatus,
|
|
28
33
|
type H3Event,
|
|
@@ -55,6 +60,8 @@ type CalendarOAuthStateOptions = {
|
|
|
55
60
|
app?: string;
|
|
56
61
|
returnUrl?: string;
|
|
57
62
|
flowId?: string;
|
|
63
|
+
desktopVerifierHash?: string;
|
|
64
|
+
desktopBrowserBindingHash?: string;
|
|
58
65
|
};
|
|
59
66
|
|
|
60
67
|
function encodeCalendarOAuthState(options: CalendarOAuthStateOptions): string {
|
|
@@ -226,6 +233,7 @@ function missingCredentialsResponse(
|
|
|
226
233
|
export const getGoogleAuthUrl = defineEventHandler(async (event: H3Event) => {
|
|
227
234
|
try {
|
|
228
235
|
const q = getQuery(event);
|
|
236
|
+
const method = getMethod(event);
|
|
229
237
|
const redirectUri = resolveOAuthRedirectUri(event);
|
|
230
238
|
if (!redirectUri) {
|
|
231
239
|
setResponseStatus(event, 400);
|
|
@@ -241,6 +249,10 @@ export const getGoogleAuthUrl = defineEventHandler(async (event: H3Event) => {
|
|
|
241
249
|
isElectron(event) || q.desktop === "1" || q.desktop === "true";
|
|
242
250
|
const mobile = q.mobile === "1" || q.mobile === "true";
|
|
243
251
|
const flowId = desktop ? (q.flow_id as string) || undefined : undefined;
|
|
252
|
+
if (method === "POST" && (!desktop || !flowId)) {
|
|
253
|
+
setResponseStatus(event, 400);
|
|
254
|
+
return { error: "Invalid desktop exchange challenge." };
|
|
255
|
+
}
|
|
244
256
|
const calendarConnect = isCalendarConnectRequest(q, owner);
|
|
245
257
|
const credentials = calendarConnect
|
|
246
258
|
? await resolveCalendarOAuthCredentials(event)
|
|
@@ -264,6 +276,31 @@ export const getGoogleAuthUrl = defineEventHandler(async (event: H3Event) => {
|
|
|
264
276
|
};
|
|
265
277
|
}
|
|
266
278
|
|
|
279
|
+
let desktopVerifierHash: string | undefined;
|
|
280
|
+
let desktopBrowserBindingHash: string | undefined;
|
|
281
|
+
if (flowId) {
|
|
282
|
+
if (method !== "POST" || q.redirect !== undefined) {
|
|
283
|
+
setResponseStatus(event, 400);
|
|
284
|
+
return { error: "Invalid desktop exchange challenge." };
|
|
285
|
+
}
|
|
286
|
+
const verifier = getHeader(event, "x-agent-native-desktop-verifier");
|
|
287
|
+
if (!verifier || q.verifier !== undefined) {
|
|
288
|
+
setResponseStatus(event, 400);
|
|
289
|
+
return { error: "Invalid desktop exchange challenge." };
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
desktopBrowserBindingHash = prepareDesktopOAuthBrowserBinding(event);
|
|
293
|
+
desktopVerifierHash = await registerDesktopExchange(
|
|
294
|
+
flowId,
|
|
295
|
+
verifier,
|
|
296
|
+
desktopBrowserBindingHash,
|
|
297
|
+
);
|
|
298
|
+
} catch {
|
|
299
|
+
setResponseStatus(event, 400);
|
|
300
|
+
return { error: "Invalid desktop exchange challenge." };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
267
304
|
const requestedReturn =
|
|
268
305
|
typeof q.return === "string" ? safeReturnPath(q.return) : "/";
|
|
269
306
|
const returnUrl = requestedReturn !== "/" ? requestedReturn : undefined;
|
|
@@ -279,6 +316,8 @@ export const getGoogleAuthUrl = defineEventHandler(async (event: H3Event) => {
|
|
|
279
316
|
app: OAUTH_STATE_APP_ID,
|
|
280
317
|
returnUrl,
|
|
281
318
|
flowId,
|
|
319
|
+
desktopVerifierHash,
|
|
320
|
+
desktopBrowserBindingHash,
|
|
282
321
|
});
|
|
283
322
|
|
|
284
323
|
const url = calendarConnect
|
|
@@ -316,6 +355,17 @@ export const handleGoogleCallback = defineEventHandler(
|
|
|
316
355
|
desktop = state.desktop ?? false;
|
|
317
356
|
mobile = state.mobile ?? false;
|
|
318
357
|
flowId = state.flowId;
|
|
358
|
+
if (
|
|
359
|
+
flowId &&
|
|
360
|
+
(!state.desktopVerifierHash ||
|
|
361
|
+
!state.desktopBrowserBindingHash ||
|
|
362
|
+
!matchesDesktopOAuthBrowserBinding(
|
|
363
|
+
event,
|
|
364
|
+
state.desktopBrowserBindingHash,
|
|
365
|
+
))
|
|
366
|
+
) {
|
|
367
|
+
throw new Error("Desktop OAuth browser binding is invalid.");
|
|
368
|
+
}
|
|
319
369
|
|
|
320
370
|
const googleError = query.error as string | undefined;
|
|
321
371
|
if (googleError) {
|
|
@@ -366,7 +416,15 @@ export const handleGoogleCallback = defineEventHandler(
|
|
|
366
416
|
);
|
|
367
417
|
|
|
368
418
|
if (flowId && sessionToken) {
|
|
369
|
-
|
|
419
|
+
if (!state.desktopVerifierHash) {
|
|
420
|
+
throw new Error("Missing desktop exchange challenge.");
|
|
421
|
+
}
|
|
422
|
+
await setDesktopExchange(
|
|
423
|
+
flowId,
|
|
424
|
+
sessionToken,
|
|
425
|
+
identity.email,
|
|
426
|
+
state.desktopVerifierHash,
|
|
427
|
+
);
|
|
370
428
|
}
|
|
371
429
|
|
|
372
430
|
return oauthCallbackResponse(event, identity.email, {
|
|
@@ -410,7 +468,15 @@ export const handleGoogleCallback = defineEventHandler(
|
|
|
410
468
|
: { sessionToken: undefined };
|
|
411
469
|
|
|
412
470
|
if (flowId && sessionToken) {
|
|
413
|
-
|
|
471
|
+
if (!state.desktopVerifierHash) {
|
|
472
|
+
throw new Error("Missing desktop exchange challenge.");
|
|
473
|
+
}
|
|
474
|
+
await setDesktopExchange(
|
|
475
|
+
flowId,
|
|
476
|
+
sessionToken,
|
|
477
|
+
sessionOwner,
|
|
478
|
+
state.desktopVerifierHash,
|
|
479
|
+
);
|
|
414
480
|
}
|
|
415
481
|
|
|
416
482
|
// 4. Return platform-appropriate response
|
|
@@ -436,10 +502,15 @@ export const getGoogleAddAccountUrl = defineEventHandler(
|
|
|
436
502
|
return { error: "Must be logged in to add an account" };
|
|
437
503
|
}
|
|
438
504
|
const q = getQuery(event);
|
|
505
|
+
const method = getMethod(event);
|
|
439
506
|
const desktop =
|
|
440
507
|
isElectron(event) || q.desktop === "1" || q.desktop === "true";
|
|
441
508
|
const mobile = q.mobile === "1" || q.mobile === "true";
|
|
442
509
|
const flowId = desktop ? (q.flow_id as string) || undefined : undefined;
|
|
510
|
+
if (method === "POST" && (!desktop || !flowId)) {
|
|
511
|
+
setResponseStatus(event, 400);
|
|
512
|
+
return { error: "Invalid desktop exchange challenge." };
|
|
513
|
+
}
|
|
443
514
|
if (!(await resolveCalendarOAuthCredentials(event))) {
|
|
444
515
|
return missingCredentialsResponse(
|
|
445
516
|
event,
|
|
@@ -456,6 +527,30 @@ export const getGoogleAddAccountUrl = defineEventHandler(
|
|
|
456
527
|
message: "redirect_uri must stay on this app's _agent-native routes.",
|
|
457
528
|
};
|
|
458
529
|
}
|
|
530
|
+
let desktopVerifierHash: string | undefined;
|
|
531
|
+
let desktopBrowserBindingHash: string | undefined;
|
|
532
|
+
if (flowId) {
|
|
533
|
+
if (method !== "POST" || q.redirect !== undefined) {
|
|
534
|
+
setResponseStatus(event, 400);
|
|
535
|
+
return { error: "Invalid desktop exchange challenge." };
|
|
536
|
+
}
|
|
537
|
+
const verifier = getHeader(event, "x-agent-native-desktop-verifier");
|
|
538
|
+
if (!verifier || q.verifier !== undefined) {
|
|
539
|
+
setResponseStatus(event, 400);
|
|
540
|
+
return { error: "Invalid desktop exchange challenge." };
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
desktopBrowserBindingHash = prepareDesktopOAuthBrowserBinding(event);
|
|
544
|
+
desktopVerifierHash = await registerDesktopExchange(
|
|
545
|
+
flowId,
|
|
546
|
+
verifier,
|
|
547
|
+
desktopBrowserBindingHash,
|
|
548
|
+
);
|
|
549
|
+
} catch {
|
|
550
|
+
setResponseStatus(event, 400);
|
|
551
|
+
return { error: "Invalid desktop exchange challenge." };
|
|
552
|
+
}
|
|
553
|
+
}
|
|
459
554
|
const state = encodeCalendarOAuthState({
|
|
460
555
|
redirectUri,
|
|
461
556
|
owner: session.email,
|
|
@@ -465,6 +560,8 @@ export const getGoogleAddAccountUrl = defineEventHandler(
|
|
|
465
560
|
addAccount: true,
|
|
466
561
|
app: OAUTH_STATE_APP_ID,
|
|
467
562
|
flowId,
|
|
563
|
+
desktopVerifierHash,
|
|
564
|
+
desktopBrowserBindingHash,
|
|
468
565
|
});
|
|
469
566
|
const url = await getAuthUrl(
|
|
470
567
|
undefined,
|
|
@@ -499,6 +596,17 @@ export const handleGoogleAddAccountCallback = defineEventHandler(
|
|
|
499
596
|
desktop = state.desktop ?? false;
|
|
500
597
|
mobile = state.mobile ?? false;
|
|
501
598
|
flowId = state.flowId;
|
|
599
|
+
if (
|
|
600
|
+
flowId &&
|
|
601
|
+
(!state.desktopVerifierHash ||
|
|
602
|
+
!state.desktopBrowserBindingHash ||
|
|
603
|
+
!matchesDesktopOAuthBrowserBinding(
|
|
604
|
+
event,
|
|
605
|
+
state.desktopBrowserBindingHash,
|
|
606
|
+
))
|
|
607
|
+
) {
|
|
608
|
+
throw new Error("Desktop OAuth browser binding is invalid.");
|
|
609
|
+
}
|
|
502
610
|
|
|
503
611
|
const googleError = query.error as string | undefined;
|
|
504
612
|
if (googleError) {
|
|
@@ -547,7 +655,15 @@ export const handleGoogleAddAccountCallback = defineEventHandler(
|
|
|
547
655
|
: { sessionToken: undefined };
|
|
548
656
|
|
|
549
657
|
if (flowId && sessionToken) {
|
|
550
|
-
|
|
658
|
+
if (!state.desktopVerifierHash) {
|
|
659
|
+
throw new Error("Missing desktop exchange challenge.");
|
|
660
|
+
}
|
|
661
|
+
await setDesktopExchange(
|
|
662
|
+
flowId,
|
|
663
|
+
sessionToken,
|
|
664
|
+
ownerEmail,
|
|
665
|
+
state.desktopVerifierHash,
|
|
666
|
+
);
|
|
551
667
|
}
|
|
552
668
|
|
|
553
669
|
return oauthCallbackResponse(event, addedEmail, {
|