Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions plugins/outlook/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
// Outlook on cloud.microsoft enforces Trusted Types (CSP). Zod's allowsEval
// feature-detect calls new Function("") the first time a zod parser runs
// (lazy, memoized via cached()). This policy just needs to exist before that
// first runtime call — import statements are hoisted regardless, but the probe
// only fires when a tool handler first invokes a zod schema at runtime.
if (typeof window !== 'undefined') {
try {
const tt = (window as Window & {
trustedTypes?: TrustedTypePolicyFactory & { defaultPolicy?: TrustedTypePolicy | null };
}).trustedTypes;
if (tt && !tt.defaultPolicy) {
tt.createPolicy('default', {
createScript: (s: string) => {
// Only allow the empty-string probe used by zod's allowsEval feature-detect.
if (s !== '') throw new TypeError('Blocked Trusted Types script conversion');
return s;
},
});
}
} catch {
// 'default' policy already exists, or Trusted Types not supported — safe to ignore.
}
}

import { OpenTabsPlugin } from '@opentabs-dev/plugin-sdk';
import type { ToolDefinition } from '@opentabs-dev/plugin-sdk';
import { isAuthenticated, waitForAuth } from './outlook-api.js';
Expand Down
9 changes: 8 additions & 1 deletion plugins/outlook/src/outlook-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,14 @@ const getAuth = (): OutlookAuth | null => {
return auth;
};

export const isAuthenticated = (): boolean => getAuth() !== null;
export const isAuthenticated = (): boolean => {
// During OAuth redirect the #code= fragment is present but MSAL tokens are
// not yet in localStorage. Return false early so the platform's 30s re-poll
// catches the token once the handshake completes, rather than burning the
// 5s isReady window on token searches that will all fail.
if (typeof window !== 'undefined' && window.location.hash.includes('code=')) return false;
return getAuth() !== null;
};

export const waitForAuth = (): Promise<boolean> =>
waitUntil(() => isAuthenticated(), { interval: 500, timeout: 5000 }).then(
Expand Down