* Create a function that communicates via window events. * * - In ISOLATED world: registers event handler that calls chrome.runtime.sendMessage * - In any world: returns a function that dispatches events and waits for response * * This allows MAIN world to call functions that require chrome.run
(handlerName: string, opts?: MakeFunctionOpts)
| 78 | * by sending events to ISOLATED world. |
| 79 | */ |
| 80 | function makeFunction<A, T>(handlerName: string, opts?: MakeFunctionOpts): (args: A) => Promise<T> { |
| 81 | const reqEvtName = `paperdebugger:req:${handlerName}`; |
| 82 | const resEvtName = `paperdebugger:res:${handlerName}`; |
| 83 | const errEvtName = `paperdebugger:err:${handlerName}`; |
| 84 | |
| 85 | // Only register the event handler in ISOLATED world (where chrome.runtime is available) |
| 86 | if (isIsolatedWorld) { |
| 87 | const eventHandler = (evt: Event) => { |
| 88 | const browserAPI = getBrowserAPI(); |
| 89 | const customEvt = evt as CustomEvent; |
| 90 | const { seq, req } = customEvt.detail; |
| 91 | if (!seq) return; |
| 92 | if (!browserAPI?.runtime?.sendMessage) return; |
| 93 | browserAPI.runtime |
| 94 | .sendMessage({ |
| 95 | action: handlerName, |
| 96 | args: req, |
| 97 | }) |
| 98 | .then((res: { error?: string }) => { |
| 99 | if (res.error) { |
| 100 | window.dispatchEvent( |
| 101 | new CustomEvent(`${errEvtName}/${seq}`, { |
| 102 | detail: { seq, err: res.error }, |
| 103 | }), |
| 104 | ); |
| 105 | } else { |
| 106 | window.dispatchEvent(new CustomEvent(`${resEvtName}/${seq}`, { detail: { seq, res } })); |
| 107 | } |
| 108 | }) |
| 109 | .catch((err: { error?: string }) => { |
| 110 | window.dispatchEvent(new CustomEvent(`${errEvtName}/${seq}`, { detail: { seq, err } })); |
| 111 | }); |
| 112 | }; |
| 113 | window.addEventListener(reqEvtName, eventHandler); |
| 114 | } |
| 115 | |
| 116 | // The callable function - works in both MAIN and ISOLATED world |
| 117 | // Dispatches event and waits for response from ISOLATED world's handler |
| 118 | const fn = (args: A): Promise<T> => { |
| 119 | const seq = uuidv4(); |
| 120 | window.dispatchEvent(new CustomEvent(reqEvtName, { detail: { seq, req: args } })); |
| 121 | return new Promise((resolve, reject) => { |
| 122 | function resListener(evt: Event): void { |
| 123 | const customEvt = evt as CustomEvent; |
| 124 | const { res } = customEvt.detail; |
| 125 | cleanup(); |
| 126 | resolve(res); |
| 127 | } |
| 128 | function errListener(evt: Event): void { |
| 129 | const customEvt = evt as CustomEvent; |
| 130 | const { err } = customEvt.detail; |
| 131 | cleanup(); |
| 132 | reject(err); |
| 133 | } |
| 134 | function cleanup(): void { |
| 135 | window.removeEventListener(`${resEvtName}/${seq}`, resListener); |
| 136 | window.removeEventListener(`${errEvtName}/${seq}`, errListener); |
| 137 | } |