Skip to content

Commit 7744bbf

Browse files
committed
Added a possibility to remove webview handlers #51
1 parent bffafc8 commit 7744bbf

File tree

12 files changed

+793
-199
lines changed

12 files changed

+793
-199
lines changed

examples/calico-colors/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@
7575
"watch": "tsc -w -p ./"
7676
},
7777
"dependencies": {
78-
"vscode-messenger": "^0.5",
79-
"vscode-messenger-webview": "^0.5"
78+
"vscode-messenger": "^0.6",
79+
"vscode-messenger-webview": "^0.6"
8080
},
8181
"devDependencies": {
8282
"browserify": "^17.0.0",

package-lock.json

Lines changed: 264 additions & 185 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/vscode-messenger-common/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vscode-messenger-common",
3-
"version": "0.5.1",
3+
"version": "0.6.0",
44
"description": "VS Code Messenger: common code shared by extension and webviews",
55
"keywords": [
66
"vscode",

packages/vscode-messenger-devtools/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change Log of `vscode-messenger-devtools`
22

3+
## v0.6.0 (Jan. 2026)
4+
5+
* Updated to use vscode-messenger v0.6.0 with enhanced handler management capabilities
6+
37
## v0.5.1 (Feb. 2025)
48

59
* Added response information to table hover. Only available if `withResponseData` property in vscode-messenger's `DiagnosticOptions` is set to `true`.

packages/vscode-messenger-devtools/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vscode-messenger-devtools",
3-
"version": "0.5.1",
3+
"version": "0.6.0",
44
"publisher": "TypeFox",
55
"displayName": "VS Code Messenger Devtools",
66
"description": "Message communication visualization for VS Code.",
@@ -50,7 +50,7 @@
5050
},
5151
"dependencies": {
5252
"@vscode/webview-ui-toolkit": "~1.0.0",
53-
"vscode-messenger": "^0.5"
53+
"vscode-messenger": "^0.6"
5454
},
5555
"devDependencies": {
5656
"@types/vscode": "^1.70.0",

packages/vscode-messenger-devtools/webview-ui/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "devtools-ui",
3-
"version": "0.5.1",
3+
"version": "0.6.0",
44
"private": true,
55
"scripts": {
66
"start": "vite",
@@ -12,7 +12,7 @@
1212
},
1313
"dependencies": {
1414
"@vscode/webview-ui-toolkit": "^1.0.0",
15-
"vscode-messenger-webview": "~0.5.0",
15+
"vscode-messenger-webview": "~0.6.0",
1616
"ag-grid-community": "~31.3.4",
1717
"ag-grid-react": "~31.3.4",
1818
"echarts": "^5.3.3",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Change Log of `vscode-messenger-webview`
2+
3+
## v0.6.0 (Jan. 2026)
4+
5+
### New Features
6+
7+
* **NEW: `unregisterHandler(method: string): boolean`** - Programmatically unregister message handlers by method name
8+
* Returns `true` if handler was successfully removed, `false` if no handler existed
9+
* Enables dynamic handler management and cleanup
10+
* **NEW: `onRequestDisposable<P, R>(type: RequestType<P, R>, handler: RequestHandler<P, R>): Disposable`** - Alternative to `onRequest()` that returns a `Disposable` for automatic cleanup
11+
* **NEW: `onNotificationDisposable<P>(type: NotificationType<P>, handler: NotificationHandler<P>): Disposable`** - Alternative to `onNotification()` that returns a `Disposable` for automatic cleanup
12+
13+
### Improvements
14+
15+
* **Enhanced JSDoc documentation** - JSDoc with examples showing different handler registration and cleanup patterns

packages/vscode-messenger-webview/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vscode-messenger-webview",
3-
"version": "0.5.1",
3+
"version": "0.6.0",
44
"description": "VS Code Messenger: webview integration",
55
"keywords": [
66
"vscode",
@@ -26,7 +26,7 @@
2626
"publish:latest": "npm publish --tag latest"
2727
},
2828
"dependencies": {
29-
"vscode-messenger-common": "^0.5"
29+
"vscode-messenger-common": "^0.6"
3030
},
3131
"devDependencies": {
3232
"jsdom": "^17.0",

packages/vscode-messenger-webview/src/messenger.ts

Lines changed: 192 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
******************************************************************************/
66

77
import type {
8-
CancellationToken,
8+
CancellationToken, Disposable,
99
JsonAny, Message, MessageParticipant, MessengerAPI,
1010
NotificationHandler, NotificationMessage, NotificationType,
1111
RequestHandler, RequestMessage, RequestType, ResponseError, ResponseMessage
@@ -42,16 +42,103 @@ export class Messenger implements MessengerAPI {
4242
this.options = { ...defaultOptions, ...options };
4343
}
4444

45+
/**
46+
* Register a request handler.
47+
* @param type The request type.
48+
* @param handler The request handler.
49+
* @returns The Messenger instance for method chaining.
50+
*
51+
* @see {@link onRequestDisposable} - Alternative method that returns a Disposable for automatic cleanup
52+
* @see {@link unregisterHandler} - Manual method to unregister handlers by method name
53+
*
54+
* @example
55+
* ```typescript
56+
* // Define message types
57+
* const myRequest: RequestType<{ userId: string }, { name: string }> = { method: 'getUser' };
58+
* const myNotification: NotificationType<string> = { method: 'statusUpdate' };
59+
*
60+
* // Method chaining approach
61+
* messenger.onRequest(myRequest, handler).onNotification(myNotification, notifHandler);
62+
*
63+
* // Manual unregistration
64+
* messenger.onRequest(myRequest, handler);
65+
* // Later...
66+
* messenger.unregisterHandler(myRequest.method);
67+
*
68+
* // Or use the disposable variant for automatic cleanup
69+
* const disposable = messenger.onRequestDisposable(myRequest, handler);
70+
* disposable.dispose(); // Clean up when done
71+
* ```
72+
*/
4573
onRequest<P, R>(type: RequestType<P, R>, handler: RequestHandler<P, R>): Messenger {
4674
this.handlerRegistry.set(type.method, handler as RequestHandler<unknown, unknown>);
4775
return this;
4876
}
4977

78+
/**
79+
* Register a request handler and return a Disposable that can be used to unregister it.
80+
* This is an alternative to onRequest() that returns a Disposable instead of the Messenger instance.
81+
*/
82+
onRequestDisposable<P, R>(type: RequestType<P, R>, handler: RequestHandler<P, R>): Disposable {
83+
this.handlerRegistry.set(type.method, handler as RequestHandler<unknown, unknown>);
84+
85+
return {
86+
dispose: () => {
87+
this.unregisterHandler(type.method);
88+
}
89+
};
90+
}
91+
92+
/**
93+
* Register a notification handler.
94+
* @param type The notification type.
95+
* @param handler The notification handler.
96+
* @returns The Messenger instance for method chaining.
97+
*
98+
* @see {@link onNotificationDisposable} - Alternative method that returns a Disposable for automatic cleanup
99+
* @see {@link unregisterHandler} - Manual method to unregister handlers by method name
100+
*
101+
* @example
102+
* ```typescript
103+
* // Define message types
104+
* const myNotification: NotificationType<{ status: string }> = { method: 'statusChanged' };
105+
* const myRequest: RequestType<string, number> = { method: 'getCount' };
106+
*
107+
* // Method chaining approach
108+
* messenger.onNotification(myNotification, handler).onRequest(myRequest, reqHandler);
109+
*
110+
* // Manual unregistration
111+
* messenger.onNotification(myNotification, handler);
112+
* // Later...
113+
* messenger.unregisterHandler(myNotification.method);
114+
*
115+
* // Or use the disposable variant for automatic cleanup
116+
* const disposable = messenger.onNotificationDisposable(myNotification, handler);
117+
* disposable.dispose(); // Clean up when done
118+
* ```
119+
*/
50120
onNotification<P>(type: NotificationType<P>, handler: NotificationHandler<P>): Messenger {
51121
this.handlerRegistry.set(type.method, handler as NotificationHandler<unknown>);
52122
return this;
53123
}
54124

125+
/**
126+
* Register a notification handler and return a Disposable that can be used to unregister it.
127+
* This is an alternative to onNotification() that returns a Disposable instead of the Messenger instance.
128+
*/
129+
onNotificationDisposable<P>(type: NotificationType<P>, handler: NotificationHandler<P>): Disposable {
130+
this.handlerRegistry.set(type.method, handler as NotificationHandler<unknown>);
131+
132+
return {
133+
dispose: () => {
134+
this.unregisterHandler(type.method);
135+
}
136+
};
137+
}
138+
139+
/**
140+
* Start the message processing.
141+
*/
55142
start(): void {
56143
if (this.started) {
57144
return;
@@ -65,6 +152,15 @@ export class Messenger implements MessengerAPI {
65152
this.started = true;
66153
}
67154

155+
/**
156+
* Unregisters a handler by its method name.
157+
* @param method The method name of the handler to unregister. Use `<Type>.method` for type safety.
158+
* @returns True if the handler was successfully unregistered, false otherwise.
159+
*/
160+
unregisterHandler(method: string): boolean {
161+
return this.handlerRegistry.delete(method);
162+
}
163+
68164
protected async processMessage(msg: Message): Promise<void> {
69165
if (msg.receiver.type === 'extension') {
70166
// Ignore the message if it's not directed to us
@@ -166,6 +262,57 @@ export class Messenger implements MessengerAPI {
166262
}
167263
}
168264

265+
/**
266+
* Send a request message to another participant and wait for a response.
267+
*
268+
* @template P The type of the request parameters
269+
* @template R The type of the response data
270+
* @param type The request type definition containing the method name
271+
* @param receiver The target participant to send the request to (extension or specific webview)
272+
* @param params Optional parameters to send with the request
273+
* @param cancelable Optional cancellation token to cancel the request
274+
* @returns A Promise that resolves with the response data or rejects if the request fails
275+
*
276+
* @throws {Error} If the receiver is a broadcast participant (broadcasts are only allowed for notifications)
277+
*
278+
* @example
279+
* ```typescript
280+
* // Define a request type
281+
* const GetUserRequest: RequestType<{ userId: string }, { name: string, email: string }> = {
282+
* method: 'getUser'
283+
* };
284+
*
285+
* // Send a request to the host extension
286+
* const user = await messenger.sendRequest(
287+
* GetUserRequest,
288+
* HOST_EXTENSION,
289+
* { userId: '123' }
290+
* );
291+
* console.log(`User: ${user.name} (${user.email})`);
292+
*
293+
* // Send a request with cancellation support
294+
* const controller = new AbortController();
295+
* const cancelToken = createCancellationToken(controller.signal);
296+
*
297+
* try {
298+
* const result = await messenger.sendRequest(
299+
* GetUserRequest,
300+
* HOST_EXTENSION,
301+
* { userId: '456' },
302+
* cancelToken
303+
* );
304+
* } catch (error) {
305+
* if (controller.signal.aborted) {
306+
* console.log('Request was cancelled');
307+
* } else {
308+
* console.error('Request failed:', error);
309+
* }
310+
* }
311+
*
312+
* // Cancel the request after 5 seconds
313+
* setTimeout(() => controller.abort('Timeout'), 5000);
314+
* ```
315+
*/
169316
sendRequest<P, R>(type: RequestType<P, R>, receiver: MessageParticipant, params?: P, cancelable?: CancellationToken): Promise<R> {
170317
if (receiver.type === 'broadcast') {
171318
throw new Error('Only notification messages are allowed for broadcast.');
@@ -199,6 +346,50 @@ export class Messenger implements MessengerAPI {
199346
return pending.result;
200347
}
201348

349+
/**
350+
* Send a notification message to another participant without expecting a response.
351+
*
352+
* Notifications are fire-and-forget messages that don't require acknowledgment or return values.
353+
* Unlike requests, notifications can be sent to broadcast receivers to notify all registered handlers.
354+
*
355+
* @template P The type of the notification parameters
356+
* @param type The notification type definition containing the method name
357+
* @param receiver The target participant to send the notification to (extension, webview, or broadcast)
358+
* @param params Optional parameters to send with the notification
359+
*
360+
* @example
361+
* ```typescript
362+
* // Define a notification type
363+
* const UserLoggedInNotification: NotificationType<{ userId: string, timestamp: number }> = {
364+
* method: 'userLoggedIn'
365+
* };
366+
*
367+
* // Send a notification to the host extension
368+
* messenger.sendNotification(
369+
* UserLoggedInNotification,
370+
* HOST_EXTENSION,
371+
* { userId: '123', timestamp: Date.now() }
372+
* );
373+
*
374+
* // Send a notification to a specific webview
375+
* messenger.sendNotification(
376+
* UserLoggedInNotification,
377+
* { type: 'webview', webviewType: 'dashboard' },
378+
* { userId: '123', timestamp: Date.now() }
379+
* );
380+
*
381+
* // Broadcast a notification to all registered handlers
382+
* messenger.sendNotification(
383+
* UserLoggedInNotification,
384+
* BROADCAST,
385+
* { userId: '123', timestamp: Date.now() }
386+
* );
387+
*
388+
* // Send a simple notification without parameters
389+
* const RefreshNotification: NotificationType<void> = { method: 'refresh' };
390+
* messenger.sendNotification(RefreshNotification, HOST_EXTENSION);
391+
* ```
392+
*/
202393
sendNotification<P>(type: NotificationType<P>, receiver: MessageParticipant, params?: P): void {
203394
const message: NotificationMessage = {
204395
method: type.method,

0 commit comments

Comments
 (0)