-
Notifications
You must be signed in to change notification settings - Fork 365
Address security and type safety issues in experimental messages implementation #181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/experimental-messages
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -141,7 +141,6 @@ describe('UIResourceRendererWC', () => { | |||
| const el = document.createElement('ui-resource-renderer'); | ||||
|
|
||||
|
||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -217,6 +217,10 @@ let _experimentalRequestId = 0; | |
| * experimental methods (e.g., "x/clipboard/write"). Standard MCP methods not yet | ||
| * in the Apps spec (e.g., "sampling/createMessage") can use their canonical names. | ||
| * @param params - Request parameters | ||
| * @param options - Optional configuration for the request | ||
| * @param options.signal - Optional AbortSignal to cancel the request | ||
| * @param options.timeout - Optional timeout in milliseconds (default: 30000) | ||
| * @param options.targetOrigin - Optional target origin for postMessage (default: '*') | ||
| * @returns Promise that resolves with the host's JSON-RPC response result, or rejects | ||
| * with the JSON-RPC error | ||
| * | ||
|
|
@@ -228,20 +232,71 @@ let _experimentalRequestId = 0; | |
| export function sendExperimentalRequest( | ||
| method: string, | ||
| params?: Record<string, unknown>, | ||
| options?: { | ||
| signal?: AbortSignal; | ||
| timeout?: number; | ||
| targetOrigin?: string; | ||
| }, | ||
| ): Promise<unknown> { | ||
| const id = ++_experimentalRequestId; | ||
| const timeout = options?.timeout ?? 30000; | ||
| const targetOrigin = options?.targetOrigin ?? '*'; | ||
|
|
||
|
Comment on lines
220
to
+244
|
||
| // Check if window.parent is available before setting up anything | ||
| if (!window.parent || window.parent === window) { | ||
| return Promise.reject(new Error('No parent window available')); | ||
| } | ||
|
|
||
| return new Promise((resolve, reject) => { | ||
| let isSettled = false; | ||
|
|
||
| const handler = (event: MessageEvent) => { | ||
| // Validate that the message comes from the parent window | ||
| if (event.source !== window.parent) { | ||
| return; | ||
| } | ||
|
|
||
| const data = event.data; | ||
| if (data?.jsonrpc === '2.0' && data?.id === id) { | ||
| window.removeEventListener('message', handler); | ||
| cleanup(); | ||
| if (data.error) { | ||
|
Comment on lines
+257
to
262
|
||
| reject(data.error); | ||
| } else { | ||
| resolve(data.result); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const abortHandler = () => { | ||
| cleanup(); | ||
| reject(new Error('Request aborted')); | ||
| }; | ||
|
|
||
| const cleanup = () => { | ||
| if (!isSettled) { | ||
| isSettled = true; | ||
| window.removeEventListener('message', handler); | ||
| clearTimeout(timeoutId); | ||
| options?.signal?.removeEventListener('abort', abortHandler); | ||
| } | ||
| }; | ||
|
|
||
| // Set up timeout | ||
| const timeoutId = setTimeout(() => { | ||
| cleanup(); | ||
| reject(new Error(`Request timeout after ${timeout}ms`)); | ||
| }, timeout); | ||
|
|
||
| // Set up abort signal | ||
| if (options?.signal) { | ||
| if (options.signal.aborted) { | ||
| cleanup(); | ||
| reject(new Error('Request aborted')); | ||
| return; | ||
| } | ||
| options.signal.addEventListener('abort', abortHandler); | ||
| } | ||
|
|
||
| window.addEventListener('message', handler); | ||
|
|
||
| window.parent.postMessage( | ||
|
|
@@ -251,7 +306,7 @@ export function sendExperimentalRequest( | |
| method, | ||
| params: params ?? {}, | ||
| }, | ||
| '*', | ||
| targetOrigin, | ||
| ); | ||
| }); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onFallbackRequestis typed asPromise<unknown>, but the result is cast toPromise<AppResult>when wiringbridge.fallbackRequestHandler. This cast bypasses type safety and makes it easy to return a value that AppBridge/transport can’t handle. Consider changingonFallbackRequestto returnPromise<AppResult>(or narrowing/validating the returned value) and removing the cast.