1use std::{collections::BTreeMap, fmt::Display};
2
3use itertools::Itertools;
4use sha3::Digest;
5pub fn gen<D: Display>(modules: &[D], rpcs: &BTreeMap<String, usize>) -> String {
6 let modules = modules.iter().map(|a| format!("{a}")).collect_vec();
7 const S: &str = r#"type Id = number;
8type Value = unknown;
9type SendMessage = (msg: string) => void;
10
11export function WSDOMConnectWebSocket(wsUrl: string | URL, wsProtocols?: string | string[]): WSDOM {
12 const ws = new WebSocket(wsUrl, wsProtocols);
13 const wsdom = new WSDOM((msg: string) => {
14 ws.send(msg);
15 });
16 ws.onopen = () => {
17 console.debug("WSDOM WebSocket connection open!");
18 console.debug("WebSocket object", ws);
19 console.debug("WSDOM object", wsdom);
20 }
21 ws.onmessage = (msg: MessageEvent<string>) => {
22 wsdom.handleIncomingMessage(msg.data);
23 };
24 ws.onclose = (ev: CloseEvent) => {
25 console.debug("WSDOM WebSocket closed", ev);
26 }
27 ws.onerror = (ev: Event) => {
28 console.warn("WSDOM WebSocket errored", ev);
29 }
30 return wsdom;
31}
32export class WSDOM {
33 public internal: WSDOMCore;
34 constructor(sendMessage: SendMessage) {
35 this.internal = new WSDOMCore(sendMessage);
36 }
37 public handleIncomingMessage(msg: string) {
38 const fn = new Function('_w', msg);
39 fn(this.internal);
40 }
41 #e
42}
43export class WSDOMCore{
44 public sender: SendMessage;
45 private values: Map<Id, { value: Value, error: boolean }>;
46 public callbacks: Map<Id,(value: Value) => void>;
47 private next_value: Id;
48 constructor(sender: SendMessage) {
49 this.sender = sender;
50 this.values = new Map();
51 this.callbacks = new Map();
52 this.next_value = Number.MAX_SAFE_INTEGER;
53 }
54 public allocate = (v: Value): Id => {
55 var i = this.next_value;
56 this.next_value--;
57 this.values.set(i,{value: v, error: false});
58 return i;
59 }
60 public a = this.allocate;
61 public g = (id: Id): Value => {
62 var w = this.values.get(id);
63 if (w?.error) {
64 throw w.value
65 } else {
66 return w?.value
67 }
68 }
69 public s = (id: Id, value: Value) => {
70 this.values.set(id, { value, error: false });
71 }
72 public d = (id: Id) => {
73 this.values.delete(id);
74 }
75 public r = (id: Id, val: Value) => {
76 const valJson = JSON.stringify(val);
77 (this.sender)(`p${id}:${valJson}`);
78 }
79 public rp = (id: Id, val: Value) => {
80 var cb = this.callbacks.get(id);
81 if(cb !== undefined){
82 cb(val)
83 }
84 }
85 public c = (id: Id): {value: Value} | {slot: Id} | undefined => {
86 var w = this.values.get(id);
87 if(w?.error){
88 return {slot: this.allocate(w.value)};
89 }else{
90 return {value: w?.value}
91 }
92 }
93 public e = (id: Id, value: Value) => {
94 this.values.set(id, { value, error: true })
95 }
96 public x: {[key: string]: Value} = {#x};
97}
98"#;
99 return format!(
100 "{}\n{}",
101 modules
102 .iter()
103 .enumerate()
104 .map(|(i, m)| format!("import * as m{i} from '{m}'"))
105 .join("\n"),
106 S.replace(
107 "#x",
108 &modules
109 .iter()
110 .enumerate()
111 .map(|(i, m)| format!(
112 "_{} :m{i} as Value",
113 hex::encode(&sha3::Sha3_256::digest(m.as_bytes()))
114 ))
115 .join(",")
116 )
117 .replace(
118 "#e",
119 &rpcs
120 .iter()
121 .map(|(a, v)| format!(
122 r#"public {a} = ({}): Promise<Value> => {{
123 return new Promise((then) => {{
124 var i = 0;
125 while(this.internal.callbacks.contains(i))i++;
126 this.internal.callbacks.set(i,then);
127 var s = `r{a}:${{i}};{}`;
128 (this.internal.sender)(s);
129 }});
130 }}"#,
131 (0usize..*v).map(|a| format!("param{a}: Value")).join(","),
132 (0usize..*v)
133 .map(|a| format!("${{this.interial.allocate(param{a})}}"))
134 .join(","),
135 ))
136 .join("\n")
137 )
138 );
139}
140pub fn launch(url: &str, path: &str, rpcs: &BTreeMap<String, usize>) -> String {
141 return format!("import WSDOMConnectWebSocket from '{path}'\nexport const WS = WSDOMConnectToServer('{url}')\n{}",rpcs.iter().map(|(a,_)|format!("export const {a} = WS.{a};")).join(";"));
142}