| 
									
										
										
										
											2025-07-27 21:48:18 -04:00
										 |  |  | /** | 
					
						
							|  |  |  |  * \file | 
					
						
							|  |  |  |  * \defgroup tfrpc Tilde Friends RPC. | 
					
						
							|  |  |  |  * Tilde Friends RPC. | 
					
						
							|  |  |  |  * @{ | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** Whether this module is being run in a web browser. */ | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | const k_is_browser = get_is_browser(); | 
					
						
							| 
									
										
										
										
											2025-07-27 21:48:18 -04:00
										 |  |  | /** Registered methods. */ | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | let g_api = {}; | 
					
						
							| 
									
										
										
										
											2025-07-27 21:48:18 -04:00
										 |  |  | /** The next method identifier. */ | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | let g_next_id = 1; | 
					
						
							| 
									
										
										
										
											2025-07-27 21:48:18 -04:00
										 |  |  | /** Identifiers of pending calls. */ | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | let g_calls = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-19 19:12:42 +01:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-30 20:25:20 -04:00
										 |  |  |  * Check if being called from a browser vs. server-side. | 
					
						
							|  |  |  |  * @return true if called from a browser. | 
					
						
							| 
									
										
										
										
											2024-02-19 19:12:42 +01:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | function get_is_browser() { | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 	try { | 
					
						
							|  |  |  | 		return window !== undefined && console !== undefined; | 
					
						
							|  |  |  | 	} catch { | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-27 21:48:18 -04:00
										 |  |  | /** \cond */ | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | if (k_is_browser) { | 
					
						
							|  |  |  | 	print = console.log; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-27 21:48:18 -04:00
										 |  |  | if (k_is_browser) { | 
					
						
							|  |  |  | 	window.addEventListener('message', function (event) { | 
					
						
							|  |  |  | 		call_rpc(event.data); | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | } else { | 
					
						
							|  |  |  | 	core.register('message', function (message) { | 
					
						
							|  |  |  | 		call_rpc(message?.message); | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export let rpc = new Proxy({}, {get: make_rpc}); | 
					
						
							|  |  |  | /** \endcond */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-19 19:12:42 +01:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-27 21:48:18 -04:00
										 |  |  |  * Make a function to invoke a remote procedure. | 
					
						
							|  |  |  |  * @param target The target. | 
					
						
							|  |  |  |  * @param prop The name of the function. | 
					
						
							|  |  |  |  * @param receiver The receiver. | 
					
						
							|  |  |  |  * @return A function. | 
					
						
							| 
									
										
										
										
											2024-02-19 19:12:42 +01:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | function make_rpc(target, prop, receiver) { | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 	return function () { | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | 		let id = g_next_id++; | 
					
						
							| 
									
										
										
										
											2023-01-21 00:16:18 +00:00
										 |  |  | 		while (!id || g_calls[id] !== undefined) { | 
					
						
							| 
									
										
										
										
											2022-08-13 18:58:06 +00:00
										 |  |  | 			id = g_next_id++; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		let promise = new Promise(function (resolve, reject) { | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | 			g_calls[id] = {resolve: resolve, reject: reject}; | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 		if (k_is_browser) { | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 			window.parent.postMessage( | 
					
						
							|  |  |  | 				{message: 'tfrpc', method: prop, params: [...arguments], id: id}, | 
					
						
							|  |  |  | 				'*' | 
					
						
							|  |  |  | 			); | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | 			return promise; | 
					
						
							|  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 			return app | 
					
						
							|  |  |  | 				.postMessage({ | 
					
						
							|  |  |  | 					message: 'tfrpc', | 
					
						
							|  |  |  | 					method: prop, | 
					
						
							|  |  |  | 					params: [...arguments], | 
					
						
							|  |  |  | 					id: id, | 
					
						
							|  |  |  | 				}) | 
					
						
							|  |  |  | 				.then((x) => promise); | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 	}; | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-19 19:12:42 +01:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-27 21:48:18 -04:00
										 |  |  |  * Send a response. | 
					
						
							|  |  |  |  * @param response The response. | 
					
						
							| 
									
										
										
										
											2024-02-19 19:12:42 +01:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-06-19 22:08:15 +00:00
										 |  |  | function send(response) { | 
					
						
							|  |  |  | 	if (k_is_browser) { | 
					
						
							|  |  |  | 		window.parent.postMessage(response, '*'); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		app.postMessage(response); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-19 19:12:42 +01:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-27 21:48:18 -04:00
										 |  |  |  * Invoke a remote procedure. | 
					
						
							|  |  |  |  * @param message An object describing the call. | 
					
						
							| 
									
										
										
										
											2024-02-19 19:12:42 +01:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | function call_rpc(message) { | 
					
						
							|  |  |  | 	if (message && message.message === 'tfrpc') { | 
					
						
							| 
									
										
										
										
											2023-01-21 00:16:18 +00:00
										 |  |  | 		let id = message.id; | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | 		if (message.method) { | 
					
						
							| 
									
										
										
										
											2023-05-19 19:47:33 +00:00
										 |  |  | 			let method = g_api[message.method]; | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | 			if (method) { | 
					
						
							|  |  |  | 				try { | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 					Promise.resolve(method(...message.params)) | 
					
						
							|  |  |  | 						.then(function (result) { | 
					
						
							|  |  |  | 							send({message: 'tfrpc', id: id, result: result}); | 
					
						
							|  |  |  | 						}) | 
					
						
							|  |  |  | 						.catch(function (error) { | 
					
						
							|  |  |  | 							send({message: 'tfrpc', id: id, error: error}); | 
					
						
							|  |  |  | 						}); | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | 				} catch (error) { | 
					
						
							| 
									
										
										
										
											2023-01-21 00:16:18 +00:00
										 |  |  | 					send({message: 'tfrpc', id: id, error: error}); | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 				send({ | 
					
						
							|  |  |  | 					message: 'tfrpc', | 
					
						
							|  |  |  | 					id: id, | 
					
						
							|  |  |  | 					error: `Method '${message.method}' not found.`, | 
					
						
							|  |  |  | 				}); | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-06-20 01:34:32 +00:00
										 |  |  | 		} else if (message.error !== undefined) { | 
					
						
							| 
									
										
										
										
											2023-01-21 00:16:18 +00:00
										 |  |  | 			if (g_calls[id]) { | 
					
						
							|  |  |  | 				g_calls[id].reject(message.error); | 
					
						
							|  |  |  | 				delete g_calls[id]; | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2023-01-21 00:16:18 +00:00
										 |  |  | 				throw new Error(id + ' not found to reply.'); | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-06-20 01:34:32 +00:00
										 |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2023-01-21 00:16:18 +00:00
										 |  |  | 			if (g_calls[id]) { | 
					
						
							|  |  |  | 				g_calls[id].resolve(message.result); | 
					
						
							|  |  |  | 				delete g_calls[id]; | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2023-01-21 00:16:18 +00:00
										 |  |  | 				throw new Error(id + ' not found to reply.'); | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-19 19:12:42 +01:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-27 21:48:18 -04:00
										 |  |  |  * Register a function that to be called remotely. | 
					
						
							|  |  |  |  * @param method The method. | 
					
						
							| 
									
										
										
										
											2024-02-19 19:12:42 +01:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-06-19 18:01:21 +00:00
										 |  |  | export function register(method) { | 
					
						
							|  |  |  | 	g_api[method.name] = method; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-07-27 21:48:18 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** @} */ |