11/* eslint-disable @typescript-eslint/no-explicit-any */
2- import { useEffect , useRef } from 'react' ;
2+ import { createContext , useEffect , useMemo , useRef } from 'react' ;
33import {
44 isViewApiError ,
55 isViewApiEvent ,
66 isViewApiResponse ,
77 type ClientCalls ,
8+ type CtxKey ,
89 type HostCalls ,
910 type RequestContext ,
1011 type ViewApiRequest ,
1112 type VsCodeApi ,
1213} from '../types' ;
1314import { generateId } from '../utils' ;
14- import { DeferredPromise , type WebviewContextValue } from './types' ;
15+ import { DeferredPromise } from './types' ;
16+ import { TypedContexts } from './useWebviewApi' ;
17+ import type { WebviewContextValue } from './WebviewContext' ;
1518
1619declare function acquireVsCodeApi ( ) : VsCodeApi ;
1720
18- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1921const vscodeApi = acquireVsCodeApi ( ) ;
2022
21- interface WebviewProviderProps {
23+ interface WebviewProviderProps < T extends ClientCalls > {
2224 viewType : string ;
2325 children : React . ReactNode ;
26+ contextKey : CtxKey < T > ;
2427}
2528
2629/**
2730 * WebviewProvider provides type-safe API access to webview components
2831 */
29- export const WebviewProvider : React . FC < WebviewProviderProps > = ( { children, viewType } ) => {
32+ export const WebviewProvider = < T extends ClientCalls , H extends HostCalls > ( {
33+ children,
34+ viewType,
35+ contextKey,
36+ } : WebviewProviderProps < T > ) => {
3037 const pendingRequests = useRef < Map < string , DeferredPromise < any > > > ( new Map ( ) ) ;
3138 const listeners = useRef < Map < keyof HostCalls , Set < ( ...args : any [ ] ) => void > > > ( new Map ( ) ) ;
3239
@@ -41,14 +48,14 @@ export const WebviewProvider: React.FC<WebviewProviderProps> = ({ children, view
4148 /**
4249 * Type-safe API caller with request/response matching
4350 */
44- const callApi = < K extends keyof ClientCalls > (
51+ const callApi = < K extends keyof T = keyof T > (
4552 key : K ,
46- ...params : Parameters < ClientCalls [ K ] >
47- ) : ReturnType < ClientCalls [ K ] > => {
53+ ...params : Parameters < T [ K ] >
54+ ) : Promise < ReturnType < T [ K ] > > => {
4855 const id = generateId ( 'req' ) ;
49- const deferred = new DeferredPromise < Awaited < ReturnType < ClientCalls [ K ] > > > ( key ) ;
56+ const deferred = new DeferredPromise < Awaited < ReturnType < T [ K ] > > > ( key as string ) ;
5057
51- const request : ViewApiRequest < K > = {
58+ const request : ViewApiRequest < T , K > = {
5259 type : 'request' ,
5360 id,
5461 key,
@@ -63,7 +70,7 @@ export const WebviewProvider: React.FC<WebviewProviderProps> = ({ children, view
6370 try {
6471 vscodeApi . postMessage ( request ) ;
6572 } catch ( error ) {
66- console . error ( `Failed to send API request ${ key } :` , error ) ;
73+ console . error ( `Failed to send API request ${ key as string } :` , error ) ;
6774 deferred . clearTimeout ( ) ;
6875 pendingRequests . current . delete ( id ) ;
6976 deferred . reject ( error instanceof Error ? error : new Error ( String ( error ) ) ) ;
@@ -75,12 +82,12 @@ export const WebviewProvider: React.FC<WebviewProviderProps> = ({ children, view
7582 /**
7683 * Create typed API object using Proxy for dynamic method access
7784 */
78- const api = new Proxy ( { } as WebviewContextValue [ 'api' ] , {
85+ const api = new Proxy ( { } as WebviewContextValue < T > [ 'api' ] , {
7986 // eslint-disable-next-line code-complete/enforce-meaningful-names
8087 get : ( _ , key : string ) => {
8188 return ( ...args : any [ ] ) => {
8289 // Type assertion is safe here because the proxy ensures correct typing at usage
83- return callApi ( key , ...( args as Parameters < ClientCalls [ keyof ClientCalls ] > ) ) ;
90+ return callApi ( key , ...( args as Parameters < T [ keyof T ] > ) ) ;
8491 } ;
8592 } ,
8693 } ) ;
@@ -107,6 +114,7 @@ export const WebviewProvider: React.FC<WebviewProviderProps> = ({ children, view
107114 * Handle messages from the extension host
108115 */
109116 useEffect ( ( ) => {
117+ // eslint-disable-next-line sonarjs/cognitive-complexity
110118 const handleMessage = ( event : MessageEvent < unknown > ) => {
111119 const message = event . data ;
112120
@@ -128,9 +136,9 @@ export const WebviewProvider: React.FC<WebviewProviderProps> = ({ children, view
128136 } else {
129137 console . warn ( `No pending request found for error ID: ${ message . id } ` ) ;
130138 }
131- } else if ( isViewApiEvent ( message ) ) {
139+ } else if ( isViewApiEvent < H > ( message ) ) {
132140 // Handle event
133- const callbacks = listeners . current . get ( message . key ) ;
141+ const callbacks = listeners . current . get ( message . key as keyof HostCalls ) ;
134142 if ( callbacks && callbacks . size > 0 ) {
135143 for ( const callback of callbacks ) {
136144 try {
@@ -172,13 +180,19 @@ export const WebviewProvider: React.FC<WebviewProviderProps> = ({ children, view
172180 } ;
173181 } , [ ] ) ;
174182
175- const contextValue : WebviewContextValue = {
183+ const context = useMemo ( ( ) => {
184+ const context = createContext < WebviewContextValue < T > | undefined > ( undefined ) ;
185+ TypedContexts . set ( contextKey , context ) ;
186+ return context ;
187+ } , [ contextKey ] ) ;
188+
189+ const contextValue : WebviewContextValue < T > = {
176190 api,
177191 addListener,
178192 removeListener,
179193 isReady : true ,
180194 vscode : vscodeApi ,
181195 } ;
182196
183- return < WebviewContext . Provider value = { contextValue } > { children } </ WebviewContext . Provider > ;
197+ return < context . Provider value = { contextValue } > { children } </ context . Provider > ;
184198} ;
0 commit comments